diff --git a/agents/radagent.go b/agents/radagent.go index ffb8861eb..60c0c9ce1 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -39,6 +39,8 @@ import ( const ( MetaRadReqType = "*radReqType" MetaRadAuth = "*radAuth" + MetaRadAccount = "*radAccount" + MetaRadReqCode = "*radReqCode" MetaRadReplyCode = "*radReplyCode" UserPasswordAVP = "User-Password" CHAPPasswordAVP = "CHAP-Password" @@ -87,11 +89,13 @@ func NewRadiusAgent(cgrCfg *config.CGRConfig, filterS *engine.FilterS, connMgr * radAgent.rsAuth[net+"://"+authAddr] = radigo.NewServer(net, authAddr, secrets, dicts, map[radigo.PacketCode]func(*radigo.Packet) (*radigo.Packet, error){ radigo.AccessRequest: radAgent.handleAuth, + radigo.StatusServer: radAgent.handleAuth, }, nil, utils.Logger) acctAddr := radAgentCfg.Listeners[i].AcctAddr radAgent.rsAcct[net+"://"+acctAddr] = radigo.NewServer(net, acctAddr, secrets, dicts, map[radigo.PacketCode]func(*radigo.Packet) (*radigo.Packet, error){ radigo.AccountingRequest: radAgent.handleAcct, + radigo.StatusServer: radAgent.handleAcct, }, nil, utils.Logger) } return radAgent, nil @@ -157,6 +161,7 @@ func (ra *RadiusAgent) handleAuth(reqPacket *radigo.Packet) (*radigo.Packet, err Type: utils.NMMapType, Map: map[string]*utils.DataNode{ utils.RemoteHost: utils.NewLeafNode(reqPacket.RemoteAddr().String()), + MetaRadReqCode: utils.NewLeafNode(reqPacket.Code.String()), }, } radDP := newRADataProvider(reqPacket) @@ -218,6 +223,8 @@ func (ra *RadiusAgent) handleAcct(reqPacket *radigo.Packet) (*radigo.Packet, err Type: utils.NMMapType, Map: map[string]*utils.DataNode{ utils.RemoteHost: utils.NewLeafNode(remoteAddr), + MetaRadReqType: utils.NewLeafNode(MetaRadAccount), + MetaRadReqCode: utils.NewLeafNode(reqPacket.Code.String()), }, } diff --git a/data/conf/samples/radius_status/cgrates.json b/data/conf/samples/radius_status/cgrates.json new file mode 100644 index 000000000..5976763ad --- /dev/null +++ b/data/conf/samples/radius_status/cgrates.json @@ -0,0 +1,78 @@ +{ + "general": { + "log_level": 7 + }, + "data_db": { + "db_type": "*internal" + }, + "stor_db": { + "db_type": "*internal" + }, + "rals": { + "enabled": true + }, + "schedulers": { + "enabled": true + }, + "cdrs": { + "enabled": true, + "rals_conns": ["*internal"] + }, + "resources": { + "enabled": true, + "store_interval": "-1" + }, + "attributes": { + "enabled": true + }, + "routes": { + "enabled": true + }, + "chargers": { + "enabled": true + }, + "sessions": { + "enabled": true, + "attributes_conns": ["*localhost"], + "cdrs_conns": ["*localhost"], + "rals_conns": ["*localhost"], + "resources_conns": ["*localhost"], + "chargers_conns": ["*internal"], + "debit_interval": "10s" + }, + "radius_agent": { + "enabled": true, + "sessions_conns": ["*localhost"], + "listeners": [ + { + "network": "udp", + "auth_address": "127.0.0.1:1812", + "acct_address": "127.0.0.1:1813" + } + ], + "request_processors": [ + { + "id": "Status", + "filters": [ + "*string:~*vars.*radReqCode:StatusServer" + ], + "flags": [ + "*none", "*log" + ], + "reply_fields": [ + { + "tag": "ReplyMessage", + "path": "*rep.Reply-Message", + "type": "*constant", + "mandatory": true, + "value": "OK" + } + ] + } + ] + }, + "apiers": { + "enabled": true, + "scheduler_conns": ["*internal"] + } +} \ No newline at end of file diff --git a/general_tests/radius_stat_it_test.go b/general_tests/radius_stat_it_test.go new file mode 100644 index 000000000..d8cf91eaf --- /dev/null +++ b/general_tests/radius_stat_it_test.go @@ -0,0 +1,190 @@ +//go:build integration +// +build integration + +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package general_tests + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/radigo" +) + +func TestRadiusStat(t *testing.T) { + switch *utils.DBType { + case utils.MetaInternal: + case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("unsupported dbtype value") + } + + var testRadiusDict = ` + ATTRIBUTE Message-Authenticator 80 octets + ATTRIBUTE User-Name 1 string + ATTRIBUTE User-Password 2 string + ATTRIBUTE CHAP-Password 3 string + ATTRIBUTE NAS-IP-Address 4 ipaddr + ATTRIBUTE NAS-Port 5 integer + ATTRIBUTE Service-Type 6 integer + ATTRIBUTE Framed-Protocol 7 integer + ATTRIBUTE Framed-IP-Address 8 ipaddr + ATTRIBUTE Framed-IP-Netmask 9 ipaddr + ATTRIBUTE Framed-Routing 10 integer + ATTRIBUTE Filter-Id 11 string + ATTRIBUTE Framed-MTU 12 integer + ATTRIBUTE Framed-Compression 13 integer + ATTRIBUTE Login-IP-Host 14 ipaddr + ATTRIBUTE Login-Service 15 integer + ATTRIBUTE Login-TCP-Port 16 integer + ATTRIBUTE Reply-Message 18 string + ATTRIBUTE Callback-Number 19 string + ATTRIBUTE Callback-Id 20 string + ATTRIBUTE Framed-Route 22 string + ATTRIBUTE Framed-IPX-Network 23 ipaddr + ATTRIBUTE State 24 string + ATTRIBUTE Class 25 string + ATTRIBUTE Vendor-Specific 26 string + ATTRIBUTE Session-Timeout 27 integer + ATTRIBUTE Idle-Timeout 28 integer + ATTRIBUTE Termination-Action 29 integer + ATTRIBUTE Called-Station-Id 30 string + ATTRIBUTE Calling-Station-Id 31 string + ATTRIBUTE NAS-Identifier 32 string + ATTRIBUTE Proxy-State 33 string + ATTRIBUTE Login-LAT-Service 34 string + ATTRIBUTE Login-LAT-Node 35 string + ATTRIBUTE Login-LAT-Group 36 string + ATTRIBUTE Framed-AppleTalk-Link 37 integer + ATTRIBUTE Framed-AppleTalk-Network 38 integer + ATTRIBUTE Framed-AppleTalk-Zone 39 string + ATTRIBUTE Acct-Status-Type 40 integer + ATTRIBUTE Acct-Delay-Time 41 integer + ATTRIBUTE Acct-Input-Octets 42 integer + ATTRIBUTE Acct-Output-Octets 43 integer + ATTRIBUTE Acct-Session-Id 44 string + ATTRIBUTE Acct-Authentic 45 integer + ATTRIBUTE Acct-Session-Time 46 integer + ATTRIBUTE Acct-Input-Packets 47 integer + ATTRIBUTE Acct-Output-Packets 48 integer + ATTRIBUTE Acct-Terminate-Cause 49 integer + ATTRIBUTE Acct-Multi-Session-Id 50 string + ATTRIBUTE Acct-Link-Count 51 integer + ATTRIBUTE Acct-Input-Gigawords 52 integer + ATTRIBUTE Acct-Output-Gigawords 53 integer + ATTRIBUTE Event-Timestamp 55 integer + ATTRIBUTE Egress-VLANID 56 string + ATTRIBUTE Ingress-Filters 57 integer + ATTRIBUTE Egress-VLAN-Name 58 string + ATTRIBUTE User-Priority-Table 59 string + ATTRIBUTE CHAP-Challenge 60 string + ATTRIBUTE NAS-Port-Type 61 integer + ATTRIBUTE Port-Limit 62 integer + ATTRIBUTE Login-LAT-Port 63 integer + ` + + dictDir := t.TempDir() + dictPath := filepath.Join(dictDir, "dictionary.test") + if err := os.WriteFile(dictPath, []byte(testRadiusDict), 0644); err != nil { + t.Fatal(err) + } + + ng := engine.TestEngine{ + ConfigPath: filepath.Join(*utils.DataDir, "conf", "samples", "radius_status"), + ConfigJSON: fmt.Sprintf(`{ + "radius_agent": { + "client_dictionaries": { + "*default": [ + %q + ] + } + } + }`, dictDir+"/"), + DBCfg: engine.InternalDBCfg, + } + + _, cfg := ng.Run(t) + + dictRad := radigo.RFC2865Dictionary() + dictRad.ParseFromReader(strings.NewReader(testRadiusDict)) + secret := cfg.RadiusAgentCfg().ClientSecrets[utils.MetaDefault] + net := cfg.RadiusAgentCfg().Listeners[0].Network + authAddr := cfg.RadiusAgentCfg().Listeners[0].AuthAddr + acctAddr := cfg.RadiusAgentCfg().Listeners[0].AcctAddr + clientAuth, err := radigo.NewClient(net, authAddr, secret, dictRad, 1, nil, utils.Logger) + if err != nil { + t.Fatal(err) + } + + req := clientAuth.NewRequest(radigo.StatusServer, 71) + + if err := req.AddAVPWithName("NAS-Identifier", "Status Check 1806. Are you alive?", ""); err != nil { + t.Fatal(err) + } + + if err := req.AddAVPWithName("Message-Authenticator", "A7kLm29qXtP4vWcE0uYdRgHsJnFbZxQ3", ""); err != nil { + t.Fatal(err) + } + + replyPacket, err := clientAuth.SendRequest(req) + if err != nil { + t.Fatal(err) + } + + if len(replyPacket.AVPs) > 1 { + t.Errorf("Expected 1 AVP, received %v AVPS", len(replyPacket.AVPs)) + } + + if replyPacket.AVPs[0].Number != 18 { + t.Errorf("Expected 18 , received %v", replyPacket.AVPs[0].Number) + } + + clientAcct, err := radigo.NewClient(net, acctAddr, secret, dictRad, 1, nil, utils.Logger) + if err != nil { + t.Fatal(err) + } + + req = clientAcct.NewRequest(radigo.StatusServer, 71) + + if err := req.AddAVPWithName("NAS-Identifier", "Status Check 1806. Are you alive?", ""); err != nil { + t.Fatal(err) + } + + if err := req.AddAVPWithName("Message-Authenticator", "A7kLm29qXtP4vWcE0uYdRgHsJnFbZxQ3", ""); err != nil { + t.Fatal(err) + } + + replyPacket, err = clientAcct.SendRequest(req) + if err != nil { + t.Fatal(err) + } + + if len(replyPacket.AVPs) > 1 { + t.Errorf("Expected 1 AVP, received %v AVPS", len(replyPacket.AVPs)) + } + + if replyPacket.AVPs[0].Number != 18 { + t.Errorf("Expected 18 , received %v", replyPacket.AVPs[0].Number) + } + +}