diff --git a/config/config.go b/config/config.go index 27f570c21..cf47b2250 100644 --- a/config/config.go +++ b/config/config.go @@ -69,6 +69,7 @@ func NewDefaultCGRConfig() (*CGRConfig, error) { cfg.SmOsipsConfig = new(SmOsipsConfig) cfg.smAsteriskCfg = new(SMAsteriskCfg) cfg.diameterAgentCfg = new(DiameterAgentCfg) + cfg.radiusAgentCfg = new(RadiusAgentCfg) cfg.ConfigReloads = make(map[string]chan struct{}) cfg.ConfigReloads[utils.CDRC] = make(chan struct{}, 1) cfg.ConfigReloads[utils.CDRC] <- struct{}{} // Unlock the channel @@ -249,6 +250,7 @@ type CGRConfig struct { SmOsipsConfig *SmOsipsConfig // SMOpenSIPS Configuration smAsteriskCfg *SMAsteriskCfg // SMAsterisk Configuration diameterAgentCfg *DiameterAgentCfg // DiameterAgent configuration + radiusAgentCfg *RadiusAgentCfg // RadiusAgent configuration HistoryServerEnabled bool // Starts History as server: . HistoryDir string // Location on disk where to store history files. HistorySaveInterval time.Duration // The timout duration between pubsub writes @@ -469,6 +471,13 @@ func (self *CGRConfig) checkConfigSanity() error { } } } + if self.radiusAgentCfg.Enabled { + for _, raSMGConn := range self.radiusAgentCfg.SMGenericConns { + if raSMGConn.Address == utils.MetaInternal && !self.SmGenericConfig.Enabled { + return errors.New("SMGeneric not enabled but referenced by RadiusAgent component") + } + } + } // ResourceLimiter checks if self.resourceLimiterCfg != nil && self.resourceLimiterCfg.Enabled { for _, connCfg := range self.resourceLimiterCfg.CDRStatConns { @@ -575,6 +584,11 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { return err } + jsnRACfg, err := jsnCfg.RadiusAgentJsonCfg() + if err != nil { + return err + } + jsnHistServCfg, err := jsnCfg.HistServJsonCfg() if err != nil { return err @@ -983,6 +997,12 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } + if jsnRACfg != nil { + if err := self.radiusAgentCfg.loadFromJsonCfg(jsnRACfg); err != nil { + return err + } + } + if jsnHistServCfg != nil { if jsnHistServCfg.Enabled != nil { self.HistoryServerEnabled = *jsnHistServCfg.Enabled @@ -1067,6 +1087,10 @@ func (self *CGRConfig) DiameterAgentCfg() *DiameterAgentCfg { return self.diameterAgentCfg } +func (self *CGRConfig) RadiusAgentCfg() *RadiusAgentCfg { + return self.radiusAgentCfg +} + // ToDo: fix locking here func (self *CGRConfig) ResourceLimiterCfg() *ResourceLimiterConfig { return self.resourceLimiterCfg diff --git a/config/config_defaults.go b/config/config_defaults.go index 3e786817c..41c3a9464 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -358,8 +358,8 @@ const CGRATES_CFG_JSON = ` "request_filter": "Subscription-Id>Subscription-Id-Type(0)", // filter requests processed by this processor "flags": [], // flags to influence processing behavior "continue_on_success": false, // continue to the next template if executed - "append_cca": true, // when continuing will append cca fields to the previous ones - "ccr_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + "append_cca": true, // when continuing will append cca fields of the the previous one + "ccr_fields":[ // import content_fields template, tag will match internally CDR field {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*users", "mandatory": true}, @@ -382,6 +382,34 @@ const CGRATES_CFG_JSON = ` }, +"radius_agent": { + "enabled": false, // enables the radius agent: + "listen_auth": "127.0.0.1:1812", // address where to listen for radius authentication requests + "listen_acct": "127.0.0.1:1813", // address where to listen for radius accounting requests + "dictionaries_dir": "/usr/share/cgrates/radius/dict/", // path towards directory holding additional dictionaries to load (extra to RFC) + "sm_generic_conns": [ + {"address": "*internal"} // connection towards SMG component for session management + ], + "create_cdr": true, // create CDR out of Accounting-Stop and send it to SMG component + "cdr_requires_session": false, // only create CDR if there is an active session at terminate + "timezone": "", // timezone for timestamps where not specified, empty for general defaults <""|UTC|Local|$IANA_TZ_DB> + "request_processors": [ + { + "id": "*default", // formal identifier of this processor + "dry_run": false, // do not send the events to SMG, just log them + "request_filter": "", // filter requests processed by this processor + "flags": [], // flags to influence processing behavior + "continue_on_success": false, // continue to the next template if executed + "append_reply": true, // when continuing will append reply fields to the next template + "request_fields":[ // import content_fields template, tag will match internally CDR field + ], + "reply_fields":[ // fields returned in radius reply + ], + }, + ], +}, + + "historys": { "enabled": false, // starts History service: . "history_dir": "/var/lib/cgrates/history", // location on disk where to store history files. diff --git a/config/config_json.go b/config/config_json.go index 31067faca..ec954e707 100644 --- a/config/config_json.go +++ b/config/config_json.go @@ -49,6 +49,7 @@ const ( KAMAILIO_JSN = "kamailio" OSIPS_JSN = "opensips" DA_JSN = "diameter_agent" + RA_JSN = "radius_agent" HISTSERV_JSN = "historys" PUBSUBSERV_JSN = "pubsubs" ALIASESSERV_JSN = "aliases" @@ -285,6 +286,18 @@ func (self CgrJsonCfg) DiameterAgentJsonCfg() (*DiameterAgentJsonCfg, error) { return cfg, nil } +func (self CgrJsonCfg) RadiusAgentJsonCfg() (*RadiusAgentJsonCfg, error) { + rawCfg, hasKey := self[RA_JSN] + if !hasKey { + return nil, nil + } + cfg := new(RadiusAgentJsonCfg) + if err := json.Unmarshal(*rawCfg, cfg); err != nil { + return nil, err + } + return cfg, nil +} + func (self CgrJsonCfg) HistServJsonCfg() (*HistServJsonCfg, error) { rawCfg, hasKey := self[HISTSERV_JSN] if !hasKey { diff --git a/config/config_json_test.go b/config/config_json_test.go index c1b11f73d..ed1cac34e 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -605,6 +605,40 @@ func TestDiameterAgentJsonCfg(t *testing.T) { } } +func TestRadiusAgentJsonCfg(t *testing.T) { + eCfg := &RadiusAgentJsonCfg{ + Enabled: utils.BoolPointer(false), + Listen_auth: utils.StringPointer("127.0.0.1:1812"), + Listen_acct: utils.StringPointer("127.0.0.1:1813"), + Dictionaries_dir: utils.StringPointer("/usr/share/cgrates/radius/dict/"), + Sm_generic_conns: &[]*HaPoolJsonCfg{ + &HaPoolJsonCfg{ + Address: utils.StringPointer(utils.MetaInternal), + }}, + Create_cdr: utils.BoolPointer(true), + Cdr_requires_session: utils.BoolPointer(false), + Timezone: utils.StringPointer(""), + Request_processors: &[]*RAReqProcessorJsnCfg{ + &RAReqProcessorJsnCfg{ + Id: utils.StringPointer("*default"), + Dry_run: utils.BoolPointer(false), + Request_filter: utils.StringPointer(""), + Flags: utils.StringSlicePointer([]string{}), + Continue_on_success: utils.BoolPointer(false), + Append_reply: utils.BoolPointer(true), + Request_fields: &[]*CdrFieldJsonCfg{}, + Reply_fields: &[]*CdrFieldJsonCfg{}, + }, + }, + } + if cfg, err := dfCgrJsonCfg.RadiusAgentJsonCfg(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCfg, cfg) { + rcv := *cfg.Request_processors + t.Errorf("Received: %+v", rcv[0].Reply_fields) + } +} + func TestDfHistServJsonCfg(t *testing.T) { eCfg := &HistServJsonCfg{ Enabled: utils.BoolPointer(false), diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 11f990151..53593348b 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -339,6 +339,30 @@ type DARequestProcessorJsnCfg struct { CCA_fields *[]*CdrFieldJsonCfg } +// Radius Agent configuration section +type RadiusAgentJsonCfg struct { + Enabled *bool + Listen_auth *string + Listen_acct *string + Dictionaries_dir *string + Sm_generic_conns *[]*HaPoolJsonCfg + Create_cdr *bool + Cdr_requires_session *bool + Timezone *string + Request_processors *[]*RAReqProcessorJsnCfg +} + +type RAReqProcessorJsnCfg struct { + Id *string + Dry_run *bool + Request_filter *string + Flags *[]string + Continue_on_success *bool + Append_reply *bool + Request_fields *[]*CdrFieldJsonCfg + Reply_fields *[]*CdrFieldJsonCfg +} + // History server config section type HistServJsonCfg struct { Enabled *bool diff --git a/config/raconfig.go b/config/raconfig.go new file mode 100644 index 000000000..3ba2d348f --- /dev/null +++ b/config/raconfig.go @@ -0,0 +1,138 @@ +/* +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 config + +import ( + "github.com/cgrates/cgrates/utils" +) + +type RadiusAgentCfg struct { + Enabled bool + ListenAuth string + ListenAcct string + DictionariesDir string + SMGenericConns []*HaPoolConfig + CreateCDR bool + CDRRequiresSession bool + Timezone string + RequestProcessors []*RARequestProcessor +} + +func (self *RadiusAgentCfg) loadFromJsonCfg(jsnCfg *RadiusAgentJsonCfg) error { + if jsnCfg == nil { + return nil + } + if jsnCfg.Enabled != nil { + self.Enabled = *jsnCfg.Enabled + } + if jsnCfg.Listen_auth != nil { + self.ListenAuth = *jsnCfg.Listen_auth + } + if jsnCfg.Listen_acct != nil { + self.ListenAcct = *jsnCfg.Listen_acct + } + if jsnCfg.Dictionaries_dir != nil { + self.DictionariesDir = *jsnCfg.Dictionaries_dir + } + if jsnCfg.Sm_generic_conns != nil { + self.SMGenericConns = make([]*HaPoolConfig, len(*jsnCfg.Sm_generic_conns)) + for idx, jsnHaCfg := range *jsnCfg.Sm_generic_conns { + self.SMGenericConns[idx] = NewDfltHaPoolConfig() + self.SMGenericConns[idx].loadFromJsonCfg(jsnHaCfg) + } + } + if jsnCfg.Create_cdr != nil { + self.CreateCDR = *jsnCfg.Create_cdr + } + if jsnCfg.Cdr_requires_session != nil { + self.CDRRequiresSession = *jsnCfg.Cdr_requires_session + } + if jsnCfg.Timezone != nil { + self.Timezone = *jsnCfg.Timezone + } + if jsnCfg.Request_processors != nil { + for _, reqProcJsn := range *jsnCfg.Request_processors { + rp := new(RARequestProcessor) + var haveID bool + for _, rpSet := range self.RequestProcessors { + if reqProcJsn.Id != nil && rpSet.Id == *reqProcJsn.Id { + rp = rpSet // Will load data into the one set + haveID = true + break + } + } + if err := rp.loadFromJsonCfg(reqProcJsn); err != nil { + return nil + } + if !haveID { + self.RequestProcessors = append(self.RequestProcessors, rp) + } + } + } + return nil +} + +// One Diameter request processor configuration +type RARequestProcessor struct { + Id string + DryRun bool + RequestFilter utils.RSRFields + Flags utils.StringMap // Various flags to influence behavior + ContinueOnSuccess bool + AppendReply bool + RequestFields []*CfgCdrField + ReplyFields []*CfgCdrField +} + +func (self *RARequestProcessor) loadFromJsonCfg(jsnCfg *RAReqProcessorJsnCfg) error { + if jsnCfg == nil { + return nil + } + if jsnCfg.Id != nil { + self.Id = *jsnCfg.Id + } + if jsnCfg.Dry_run != nil { + self.DryRun = *jsnCfg.Dry_run + } + var err error + if jsnCfg.Request_filter != nil { + if self.RequestFilter, err = utils.ParseRSRFields(*jsnCfg.Request_filter, utils.INFIELD_SEP); err != nil { + return err + } + } + if jsnCfg.Flags != nil { + self.Flags = utils.StringMapFromSlice(*jsnCfg.Flags) + } + if jsnCfg.Continue_on_success != nil { + self.ContinueOnSuccess = *jsnCfg.Continue_on_success + } + if jsnCfg.Append_reply != nil { + self.AppendReply = *jsnCfg.Append_reply + } + if jsnCfg.Request_fields != nil { + if self.RequestFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Request_fields); err != nil { + return err + } + } + if jsnCfg.Reply_fields != nil { + if self.ReplyFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Reply_fields); err != nil { + return err + } + } + return nil +}