From ca6a9440f7a0bf50569a5acceac2eda1db8c58c8 Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Tue, 5 Mar 2024 11:09:38 -0500 Subject: [PATCH] Update client_da_addresses structure Supports configuring transport (tcp/udp), host, port and flags (only *log for now). --- agents/radagent.go | 20 ++++-- agents/radius_coa_it_test.go | 2 +- config/config_defaults.go | 30 +++++--- config/config_json_test.go | 2 +- config/libconfig_json.go | 9 ++- config/radiuscfg.go | 69 ++++++++++++++++--- .../radius_coa_disconnect/cgrates.json | 10 ++- utils/consts.go | 2 + 8 files changed, 114 insertions(+), 30 deletions(-) diff --git a/agents/radagent.go b/agents/radagent.go index c2d343d24..91e01e664 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net" + "strconv" "strings" "sync" @@ -575,12 +576,13 @@ func (ra *RadiusAgent) sendRadDaReq(requestType radigo.PacketCode, requestTempla return 0, fmt.Errorf("could not set attributes: %w", err) } - remoteAddr, remoteHost, err := dmRemoteAddr(packet.RemoteAddr().String(), + remoteAddr, remoteHost, err := daRequestAddress(packet.RemoteAddr().String(), ra.cgrCfg.RadiusAgentCfg().ClientDaAddresses) if err != nil { return 0, fmt.Errorf("retrieving remote address failed: %w", err) } - dynAuthClient, err := radigo.NewClient(utils.UDP, remoteAddr, + clientOpts := ra.cgrCfg.RadiusAgentCfg().ClientDaAddresses[remoteHost] + dynAuthClient, err := radigo.NewClient(clientOpts.Transport, remoteAddr, ra.dacCfg.secrets.GetSecret(remoteHost), ra.dacCfg.dicts.GetInstance(remoteHost), ra.cgrCfg.GeneralCfg().ConnectAttempts, nil, utils.Logger) @@ -591,6 +593,11 @@ func (ra *RadiusAgent) sendRadDaReq(requestType radigo.PacketCode, requestTempla if err = radAppendAttributes(dynAuthReq, agReq.radDAReq); err != nil { return 0, fmt.Errorf("could not append attributes to the request packet: %w", err) } + if clientOpts.Flags.Has(utils.MetaLog) { + utils.Logger.Info( + fmt.Sprintf("<%s> LOG, sending %s for session with ID '%s' to '%s': %s", + utils.RadiusAgent, requestType, sessionID, remoteAddr, utils.ToJSON(dynAuthReq))) + } dynAuthReply, err := dynAuthClient.SendRequest(dynAuthReq) if err != nil { return 0, fmt.Errorf("failed to send request: %w", err) @@ -598,9 +605,9 @@ func (ra *RadiusAgent) sendRadDaReq(requestType radigo.PacketCode, requestTempla return dynAuthReply.Code, nil } -// dmRemoteAddr ranges over the client_da_addresses map and returns the address configured for a +// daRequestAddress ranges over the client_da_addresses map and returns the address configured for a // specific client alongside the host. -func dmRemoteAddr(remoteAddr string, dynAuthAddresses map[string]string) (string, string, error) { +func daRequestAddress(remoteAddr string, dynAuthAddresses map[string]config.DAClientOpts) (string, string, error) { if len(dynAuthAddresses) == 0 { return "", "", utils.ErrNotFound } @@ -608,9 +615,10 @@ func dmRemoteAddr(remoteAddr string, dynAuthAddresses map[string]string) (string if err != nil { return "", "", err } - for host, addr := range dynAuthAddresses { + for host, opts := range dynAuthAddresses { if host == remoteHost { - return addr, host, nil + address := opts.Host + ":" + strconv.Itoa(opts.Port) + return address, host, nil } } return "", "", utils.ErrNotFound diff --git a/agents/radius_coa_it_test.go b/agents/radius_coa_it_test.go index 5efad946c..df59e01f7 100644 --- a/agents/radius_coa_it_test.go +++ b/agents/radius_coa_it_test.go @@ -186,7 +186,7 @@ func TestRadiusCoADisconnect(t *testing.T) { var testRadClient testNAS secrets := radigo.NewSecrets(map[string]string{utils.MetaDefault: "CGRateS.org"}) dicts := radigo.NewDictionaries(map[string]*radigo.Dictionary{utils.MetaDefault: dictRad}) - testRadClient.server = radigo.NewServer("udp", "127.0.0.1:3799", secrets, dicts, + testRadClient.server = radigo.NewServer(utils.UDP, "127.0.0.1:3799", secrets, dicts, map[radigo.PacketCode]func(*radigo.Packet) (*radigo.Packet, error){ radigo.DisconnectRequest: handleDisconnect, radigo.CoARequest: handleCoA, diff --git a/config/config_defaults.go b/config/config_defaults.go index 7cce03a18..ec3cfa576 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -695,27 +695,35 @@ const CGRATES_CFG_JSON = ` "radius_agent": { - "enabled": false, // enables the radius agent: + "enabled": false, // enables the radius agent: "listeners":[ { - "network": "udp", // network to listen on - "auth_address": "127.0.0.1:1812", // address where to listen for radius authentication requests - "acct_address": "127.0.0.1:1813" // address where to listen for radius accounting requests + "network": "udp", // network to listen on + "auth_address": "127.0.0.1:1812", // address where to listen for radius authentication requests + "acct_address": "127.0.0.1:1813" // address where to listen for radius accounting requests } ], - "client_secrets": { // hash containing secrets for clients connecting here <*default|$client_ip> + "client_secrets": { // hash containing secrets for clients connecting here <*default|$client_ip> "*default": "CGRateS.org" }, - "client_dictionaries": { // per client path towards directory holding additional dictionaries to load (extra to RFC) - "*default": [ // key represents the client IP or catch-all <*default|$client_ip> + "client_dictionaries": { // per client path towards directory holding additional dictionaries to load (extra to RFC) + "*default": [ // key represents the client IP or catch-all <*default|$client_ip> "/usr/share/cgrates/radius/dict/", ] }, - "client_da_addresses": {}, // list of clients supporting dynamic authorization + "client_da_addresses": { // configuration for clients capable of handling Dynamic Authorization (CoA/DM) requests. + // "nasIdentifier": { // identifier for the NAS, typically the host from the initial RADIUS packet. + // "transport": "udp", // transport protocol for Dynamic Authorization requests, defaults to UDP. + // "host": "", // optionally specify an alternative host for DA requests. Defaults to the NAS identifier if empty. + // "port": 3799, // port for Dynamic Authorization requests, default is 3799. + // "flags": [] // additional options, currently supports *log for logging DA requests before sending. + // } + }, + "requests_cache_key": "", "sessions_conns": ["*internal"], - "dmr_template": "", // template used to build the Disconnect-Request packet - "coa_template": "", // template used to build the CoA-Request packet - "request_processors": [ // request processors to be applied to Radius messages + "dmr_template": "", // template used to build the Disconnect-Request packet + "coa_template": "", // template used to build the CoA-Request packet + "request_processors": [ // request processors to be applied to Radius messages ] }, diff --git a/config/config_json_test.go b/config/config_json_test.go index 0583ce4f7..c9eb692a0 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -1000,7 +1000,7 @@ func TestRadiusAgentJsonCfg(t *testing.T) { Request_processors: &[]*ReqProcessorJsnCfg{}, Dmr_template: utils.StringPointer(""), Coa_template: utils.StringPointer(""), - Client_da_addresses: map[string]string{}, + Client_da_addresses: map[string]DAClientOptsJson{}, } dfCgrJSONCfg, err := NewCgrJsonCfgFromBytes([]byte(CGRATES_CFG_JSON)) if err != nil { diff --git a/config/libconfig_json.go b/config/libconfig_json.go index c01fcaa90..0b4fda8fa 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -532,13 +532,20 @@ type RadiListenerJsnCfg struct { Acct_Address *string } +type DAClientOptsJson struct { + Transport *string + Host *string + Port *int + Flags []string +} + // Radius Agent configuration section type RadiusAgentJsonCfg struct { Enabled *bool Listeners *[]*RadiListenerJsnCfg Client_secrets *map[string]string Client_dictionaries *map[string][]string - Client_da_addresses map[string]string + Client_da_addresses map[string]DAClientOptsJson Sessions_conns *[]string Dmr_template *string Coa_template *string diff --git a/config/radiuscfg.go b/config/radiuscfg.go index a365a2f69..3e3c62f7a 100644 --- a/config/radiuscfg.go +++ b/config/radiuscfg.go @@ -34,7 +34,7 @@ type RadiusAgentCfg struct { Listeners []RadiusListener ClientSecrets map[string]string ClientDictionaries map[string][]string - ClientDaAddresses map[string]string + ClientDaAddresses map[string]DAClientOpts SessionSConns []string DMRTemplate string CoATemplate string @@ -82,10 +82,13 @@ func (ra *RadiusAgentCfg) loadFromJSONCfg(jsnCfg *RadiusAgentJsonCfg, separator } if len(jsnCfg.Client_da_addresses) != 0 { if ra.ClientDaAddresses == nil { - ra.ClientDaAddresses = make(map[string]string) + ra.ClientDaAddresses = make(map[string]DAClientOpts) } - for k, v := range jsnCfg.Client_da_addresses { - ra.ClientDaAddresses[k] = v + ra.ClientDaAddresses = make(map[string]DAClientOpts, len(jsnCfg.Client_da_addresses)) + for hostKey, clientOpts := range jsnCfg.Client_da_addresses { + cfg := DAClientOpts{} + cfg.loadFromJSONCfg(clientOpts, hostKey) + ra.ClientDaAddresses[hostKey] = cfg } } if jsnCfg.Sessions_conns != nil { @@ -177,9 +180,9 @@ func (ra *RadiusAgentCfg) AsMapInterface(separator string) (initialMP map[string } initialMP[utils.ClientDictionariesCfg] = clientDictionaries if len(ra.ClientDaAddresses) != 0 { - clientDaAddresses := make(map[string]string) + clientDaAddresses := make(map[string]any) for k, v := range ra.ClientDaAddresses { - clientDaAddresses[k] = v + clientDaAddresses[k] = v.AsMapInterface() } initialMP[utils.ClientDaAddressesCfg] = clientDaAddresses } @@ -213,9 +216,9 @@ func (ra RadiusAgentCfg) Clone() (cln *RadiusAgentCfg) { cln.ClientDictionaries[k] = v } if len(ra.ClientDaAddresses) != 0 { - cln.ClientDaAddresses = make(map[string]string) + cln.ClientDaAddresses = make(map[string]DAClientOpts, len(ra.ClientDaAddresses)) for k, v := range ra.ClientDaAddresses { - cln.ClientDaAddresses[k] = v + cln.ClientDaAddresses[k] = *v.Clone() } } if ra.RequestProcessors != nil { @@ -226,3 +229,53 @@ func (ra RadiusAgentCfg) Clone() (cln *RadiusAgentCfg) { } return } + +type DAClientOpts struct { + Transport string // transport protocol for Dynamic Authorization requests . + Host string // alternative host for DA requests + Port int // port for Dynamic Authorization requests + Flags utils.FlagsWithParams // flags (only *log for now) +} + +func (cda *DAClientOpts) loadFromJSONCfg(jsnCfg DAClientOptsJson, defaultHost string) error { + cda.Transport = utils.UDP + if jsnCfg.Transport != nil { + cda.Transport = *jsnCfg.Transport + } + cda.Host = defaultHost + if jsnCfg.Host != nil { + cda.Host = *jsnCfg.Host + } + cda.Port = 3799 + if jsnCfg.Port != nil { + cda.Port = *jsnCfg.Port + } + if jsnCfg.Flags != nil { + cda.Flags = utils.FlagsWithParamsFromSlice(jsnCfg.Flags) + } + return nil +} + +func (cda *DAClientOpts) Clone() *DAClientOpts { + cln := DAClientOpts{ + Transport: cda.Transport, + Host: cda.Host, + Port: cda.Port, + } + if cda.Flags != nil { + cln.Flags = cda.Flags.Clone() + } + return &cln +} + +func (cda *DAClientOpts) AsMapInterface() map[string]any { + mp := map[string]any{ + utils.TransportCfg: cda.Transport, + utils.HostCfg: cda.Host, + utils.PortCfg: cda.Port, + } + if len(cda.Flags) != 0 { + mp[utils.FlagsCfg] = cda.Flags.SliceFlags() + } + return mp +} diff --git a/data/conf/samples/radius_coa_disconnect/cgrates.json b/data/conf/samples/radius_coa_disconnect/cgrates.json index 159528cd6..144731a63 100644 --- a/data/conf/samples/radius_coa_disconnect/cgrates.json +++ b/data/conf/samples/radius_coa_disconnect/cgrates.json @@ -58,8 +58,13 @@ "radius_agent": { "enabled": true, "sessions_conns": ["*bijson_localhost"], - "client_da_addresses": { - "127.0.0.1": ":3799" + "client_da_addresses": { + "127.0.0.1": { + "transport": "udp", + "host": "", + "port": 3799, + "flags": ["*log"] + } }, "listeners":[ { @@ -78,3 +83,4 @@ } } + diff --git a/utils/consts.go b/utils/consts.go index f7e9d68d8..0c329b94a 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -2261,6 +2261,8 @@ const ( ClientDaAddressesCfg = "client_da_addresses" DMRTemplateCfg = "dmr_template" CoATemplateCfg = "coa_template" + HostCfg = "host" + PortCfg = "port" // AttributeSCfg IndexedSelectsCfg = "indexed_selects"