diff --git a/agents/dnsagent.go b/agents/dnsagent.go index 2f5d97c32..a457a9186 100644 --- a/agents/dnsagent.go +++ b/agents/dnsagent.go @@ -17,3 +17,57 @@ along with this program. If not, see */ package agents + +import ( + "fmt" + "strings" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/rpcclient" + "github.com/miekg/dns" +) + +// NewDNSAgent is the constructor for DNSAgent +func NewDNSAgent(cgrCfg *config.CGRConfig, fltrS *engine.FilterS, + sS rpcclient.RpcClientConnection) (da *DNSAgent, err error) { + da = &DNSAgent{cgrCfg: cgrCfg, fltrS: fltrS, sS: sS} + return +} + +// DNSAgent translates DNS requests towards CGRateS infrastructure +type DNSAgent struct { + cgrCfg *config.CGRConfig // loaded CGRateS configuration + fltrS *engine.FilterS // connection towards FilterS + sS rpcclient.RpcClientConnection // connection towards CGR-SessionS component +} + +// ListenAndServe will run the DNS handler doing also the connection to listen address +func (da *DNSAgent) ListenAndServe() error { + if strings.HasSuffix(da.cgrCfg.DNSAgentCfg().ListenNet, utils.TLSNoCaps) { + return dns.ListenAndServeTLS( + da.cgrCfg.DNSAgentCfg().Listen, + da.cgrCfg.TlsCfg().ServerCerificate, + da.cgrCfg.TlsCfg().ServerKey, + dns.HandlerFunc( + func(w ResponseWriter, m *Msg) { + go da.handleMessage(w, m) + }), + ) + } + return dns.ListenAndServe( + da.cgrCfg.DNSAgentCfg().Listen, + da.cgrCfg.DNSAgentCfg().ListenNet, + dns.HandlerFunc( + func(w ResponseWriter, m *Msg) { + go da.handleMessage(w, m) + }), + ) +} + +// handleMessage is the entry point of all DNS requests +// requests are reaching here asynchronously +func (da *DNSAgent) handleMessage(w ResponseWriter, m *dns.Msg) { + fmt.Printf("got message: %+v\n", m) +} diff --git a/config/config.go b/config/config.go index eb7d4800b..79465a6b3 100755 --- a/config/config.go +++ b/config/config.go @@ -152,6 +152,7 @@ func NewDefaultCGRConfig() (*CGRConfig, error) { cfg.asteriskAgentCfg = new(AsteriskAgentCfg) cfg.diameterAgentCfg = new(DiameterAgentCfg) cfg.radiusAgentCfg = new(RadiusAgentCfg) + cfg.dnsAgentCfg = new(DNSAgentCfg) cfg.attributeSCfg = new(AttributeSCfg) cfg.chargerSCfg = new(ChargerSCfg) cfg.resourceSCfg = new(ResourceSConfig) @@ -324,6 +325,7 @@ type CGRConfig struct { asteriskAgentCfg *AsteriskAgentCfg // AsteriskAgent config diameterAgentCfg *DiameterAgentCfg // DiameterAgent config radiusAgentCfg *RadiusAgentCfg // RadiusAgent config + dnsAgentCfg *DNSAgentCfg // DNSAgent config attributeSCfg *AttributeSCfg // AttributeS config chargerSCfg *ChargerSCfg // ChargerS config resourceSCfg *ResourceSConfig // ResourceS config @@ -605,6 +607,13 @@ func (self *CGRConfig) checkConfigSanity() error { } } } + if self.dnsAgentCfg.Enabled && !self.sessionSCfg.Enabled { + for _, sSConn := range self.dnsAgentCfg.SessionSConns { + if sSConn.Address == utils.MetaInternal { + return fmt.Errorf("%s not enabled but referenced by %s", utils.SessionS, utils.DNSAgent) + } + } + } // HTTPAgent checks for _, httpAgentCfg := range self.httpAgentCfg { // httpAgent checks @@ -840,6 +849,14 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) (err error) { return err } + jsnDNSCfg, err := jsnCfg.DNSAgentJsonCfg() + if err != nil { + return err + } + if err := self.dnsAgentCfg.loadFromJsonCfg(jsnDNSCfg, self.generalCfg.RsrSepatarot); err != nil { + return err + } + jsnHttpAgntCfg, err := jsnCfg.HttpAgentJsonCfg() if err != nil { return err @@ -1050,6 +1067,10 @@ func (self *CGRConfig) RadiusAgentCfg() *RadiusAgentCfg { return self.radiusAgentCfg } +func (self *CGRConfig) DNSAgentCfg() *DNSAgentCfg { + return self.dnsAgentCfg +} + func (cfg *CGRConfig) AttributeSCfg() *AttributeSCfg { return cfg.attributeSCfg } diff --git a/config/config_defaults.go b/config/config_defaults.go index 20215a3ae..127c1d471 100755 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -453,6 +453,19 @@ const CGRATES_CFG_JSON = ` ], +"dns_agent": { + "enabled": false, // enables the DNS agent: + "listen_net": "udp", // network to listen on + "listen": "127.0.0.1:53", // address where to listen for DNS requests + "sessions_conns": [ // connections to SessionS for session management and CDR posting + {"address": "*internal"} + ], + "timezone": "", // timezone of the events if not specified + "request_processors": [ // request processors to be applied to DNS messages + ], +}, + + "attributes": { // AttributeS config "enabled": false, // starts attribute service: . "indexed_selects":true, // enable profile matching exclusively on indexes diff --git a/config/config_json.go b/config/config_json.go index f4039e71a..280dd0dd4 100644 --- a/config/config_json.go +++ b/config/config_json.go @@ -66,6 +66,7 @@ const ( TlsCfgJson = "tls" AnalyzerCfgJson = "analyzers" Apier = "apier" + DNSAgentJson = "dns_agent" ) // Loads the json config out of io.Reader, eg other sources than file, maybe over http @@ -320,6 +321,16 @@ func (self CgrJsonCfg) HttpAgentJsonCfg() (*[]*HttpAgentJsonCfg, error) { return &httpAgnt, nil } +func (self CgrJsonCfg) DNSAgentJsonCfg() (da *DNSAgentJsonCfg, err error) { + rawCfg, hasKey := self[DNSAgentJson] + if !hasKey { + return + } + da = new(DNSAgentJsonCfg) + err = json.Unmarshal(*rawCfg, da) + return +} + func (cgrJsn CgrJsonCfg) AttributeServJsonCfg() (*AttributeSJsonCfg, error) { rawCfg, hasKey := cgrJsn[ATTRIBUTE_JSN] if !hasKey { diff --git a/config/config_json_test.go b/config/config_json_test.go index f5b2bef36..48fe9daf2 100755 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -737,6 +737,26 @@ func TestHttpAgentJsonCfg(t *testing.T) { } } +func TestDNSAgentJsonCfg(t *testing.T) { + eCfg := &DNSAgentJsonCfg{ + Enabled: utils.BoolPointer(false), + Listen_net: utils.StringPointer("udp"), + Listen: utils.StringPointer("127.0.0.1:53"), + Sessions_conns: &[]*HaPoolJsonCfg{ + { + Address: utils.StringPointer(utils.MetaInternal), + }}, + Timezone: utils.StringPointer(""), + Request_processors: &[]*ReqProcessorJsnCfg{}, + } + if cfg, err := dfCgrJsonCfg.DNSAgentJsonCfg(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCfg, cfg) { + rcv := *cfg.Request_processors + t.Errorf("Received: %+v", rcv) + } +} + func TestDfAttributeServJsonCfg(t *testing.T) { eCfg := &AttributeSJsonCfg{ Enabled: utils.BoolPointer(false), diff --git a/config/dnsagntcfg.go b/config/dnsagntcfg.go new file mode 100644 index 000000000..34de430cd --- /dev/null +++ b/config/dnsagntcfg.go @@ -0,0 +1,122 @@ +/* +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 DNSAgentCfg struct { + Enabled bool + ListenNet string // udp or tcp + Listen string + SessionSConns []*RemoteHost + Timezone string + RequestProcessors []*RequestProcessor +} + +func (da *DNSAgentCfg) loadFromJsonCfg(jsnCfg *DNSAgentJsonCfg, sep string) (err error) { + if jsnCfg == nil { + return nil + } + if jsnCfg.Enabled != nil { + da.Enabled = *jsnCfg.Enabled + } + if jsnCfg.Listen_net != nil { + da.ListenNet = *jsnCfg.Listen_net + } + if jsnCfg.Listen != nil { + da.Listen = *jsnCfg.Listen + } + if jsnCfg.Sessions_conns != nil { + da.SessionSConns = make([]*RemoteHost, len(*jsnCfg.Sessions_conns)) + for idx, jsnHaCfg := range *jsnCfg.Sessions_conns { + da.SessionSConns[idx] = NewDfltRemoteHost() + da.SessionSConns[idx].loadFromJsonCfg(jsnHaCfg) + } + } + if jsnCfg.Request_processors != nil { + for _, reqProcJsn := range *jsnCfg.Request_processors { + rp := new(RequestProcessor) + var haveID bool + for _, rpSet := range da.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, sep); err != nil { + return nil + } + if !haveID { + da.RequestProcessors = append(da.RequestProcessors, rp) + } + } + } + return nil +} + +// One request processor configuration +type RequestProcessor struct { + ID string + Tenant RSRParsers + Filters []string + Flags utils.StringMap + ContinueOnSuccess bool + RequestFields []*FCTemplate + ReplyFields []*FCTemplate +} + +func (rp *RequestProcessor) loadFromJsonCfg(jsnCfg *ReqProcessorJsnCfg, sep string) (err error) { + if jsnCfg == nil { + return nil + } + if jsnCfg.ID != nil { + rp.ID = *jsnCfg.ID + } + if jsnCfg.Filters != nil { + rp.Filters = make([]string, len(*jsnCfg.Filters)) + for i, fltr := range *jsnCfg.Filters { + rp.Filters[i] = fltr + } + } + if jsnCfg.Flags != nil { + rp.Flags = utils.StringMapFromSlice(*jsnCfg.Flags) + } + if jsnCfg.Continue_on_success != nil { + rp.ContinueOnSuccess = *jsnCfg.Continue_on_success + } + if jsnCfg.Tenant != nil { + if rp.Tenant, err = NewRSRParsers(*jsnCfg.Tenant, true, sep); err != nil { + return err + } + } + if jsnCfg.Request_fields != nil { + if rp.RequestFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Request_fields, sep); err != nil { + return + } + } + if jsnCfg.Reply_fields != nil { + if rp.ReplyFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Reply_fields, sep); err != nil { + return + } + } + return nil +} diff --git a/config/libconfig_json.go b/config/libconfig_json.go index ebf3704ca..77fc94683 100755 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -360,7 +360,6 @@ type RadiusAgentJsonCfg struct { Client_secrets *map[string]string Client_dictionaries *map[string]string Sessions_conns *[]*HaPoolJsonCfg - Tenant *string Timezone *string Request_processors *[]*RAReqProcessorJsnCfg } @@ -397,11 +396,25 @@ type HttpAgentProcessorJsnCfg struct { Reply_fields *[]*FcTemplateJsonCfg } -// History server config section -type HistServJsonCfg struct { - Enabled *bool - History_dir *string - Save_interval *string +// DNSAgentJsonCfg +type DNSAgentJsonCfg struct { + Enabled *bool + Listen *string + Listen_net *string + Sessions_conns *[]*HaPoolJsonCfg + Timezone *string + Request_processors *[]*ReqProcessorJsnCfg +} + +type ReqProcessorJsnCfg struct { + ID *string + Filters *[]string + Tenant *string + Timezone *string + Flags *[]string + Continue_on_success *bool + Request_fields *[]*FcTemplateJsonCfg + Reply_fields *[]*FcTemplateJsonCfg } // Attribute service config section diff --git a/utils/consts.go b/utils/consts.go index 7b105d629..4a092bea8 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -562,6 +562,8 @@ const ( MetaRemove = "*remove" MetaClear = "*clear" LoadIDs = "load_ids" + DNSAgent = "DNSAgent" + TLSNoCaps = "tls" ) // Migrator Action