Files
cgrates/agents/dnsagent.go
2021-10-08 11:12:04 +03:00

164 lines
5.2 KiB
Go

/*
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 <http://www.gnu.org/licenses/>
*/
package agents
import (
"crypto/tls"
"fmt"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/miekg/dns"
)
// NewDNSAgent is the constructor for DNSAgent
func NewDNSAgent(cgrCfg *config.CGRConfig, fltrS *engine.FilterS,
connMgr *engine.ConnManager) (da *DNSAgent, err error) {
da = &DNSAgent{cgrCfg: cgrCfg, fltrS: fltrS, connMgr: connMgr}
err = da.initDNSServer()
return
}
// DNSAgent translates DNS requests towards CGRateS infrastructure
type DNSAgent struct {
cgrCfg *config.CGRConfig // loaded CGRateS configuration
fltrS *engine.FilterS // connection towards FilterS
server *dns.Server
connMgr *engine.ConnManager
}
// initDNSServer instantiates the DNS server
func (da *DNSAgent) initDNSServer() (_ error) {
da.server = &dns.Server{
Addr: da.cgrCfg.DNSAgentCfg().Listen,
Net: da.cgrCfg.DNSAgentCfg().ListenNet,
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
go da.handleMessage(w, m)
}),
}
if strings.HasSuffix(da.cgrCfg.DNSAgentCfg().ListenNet, utils.TLSNoCaps) {
cert, err := tls.LoadX509KeyPair(da.cgrCfg.TLSCfg().ServerCerificate, da.cgrCfg.TLSCfg().ServerKey)
if err != nil {
return err
}
da.server.Net = "tcp-tls"
da.server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
}
return
}
// ListenAndServe will run the DNS handler doing also the connection to listen address
func (da *DNSAgent) ListenAndServe() (err error) {
utils.Logger.Info(fmt.Sprintf("<%s> start listening on <%s:%s>",
utils.DNSAgent, da.cgrCfg.DNSAgentCfg().ListenNet, da.cgrCfg.DNSAgentCfg().Listen))
return da.server.ListenAndServe()
}
// Reload will reinitialize the server
// this is in order to monitor if we receive error on ListenAndServe
func (da *DNSAgent) Reload() (err error) {
return da.initDNSServer()
}
// handleMessage is the entry point of all DNS requests
// requests are reaching here asynchronously
func (da *DNSAgent) handleMessage(w dns.ResponseWriter, req *dns.Msg) {
dnsDP := newDnsDP(req)
rply := newDnsReply(req)
rmtAddr := w.RemoteAddr().String()
for _, q := range req.Question {
if processed, err := da.handleQuestion(dnsDP, rply, &q, rmtAddr); err != nil ||
!processed {
rply := newDnsReply(req)
rply.Rcode = dns.RcodeServerFailure
dnsWriteMsg(w, rply)
return
}
}
if err := dnsWriteMsg(w, rply); err != nil { // failed sending, most probably content issue
rply := newDnsReply(req)
rply.Rcode = dns.RcodeServerFailure
dnsWriteMsg(w, rply)
}
}
// Shutdown stops the DNS server
func (da *DNSAgent) Shutdown() error {
return da.server.Shutdown()
}
// handleMessage is the entry point of all DNS requests
// requests are reaching here asynchronously
func (da *DNSAgent) handleQuestion(dnsDP utils.DataProvider, rply *dns.Msg, q *dns.Question, rmtAddr string) (processed bool, err error) {
reqVars := &utils.DataNode{
Type: utils.NMMapType,
Map: map[string]*utils.DataNode{
utils.DNSQueryType: utils.NewLeafNode(dns.TypeToString[q.Qtype]),
utils.DNSQueryName: utils.NewLeafNode(q.Name),
utils.RemoteHost: utils.NewLeafNode(rmtAddr),
},
}
// message preprocesing
cgrRplyNM := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
rplyNM := utils.NewOrderedNavigableMap() // share it among different processors
opts := utils.MapStorage{}
for _, reqProcessor := range da.cgrCfg.DNSAgentCfg().RequestProcessors {
var lclProcessed bool
if lclProcessed, err = processRequest(
reqProcessor,
NewAgentRequest(
dnsDP, reqVars, cgrRplyNM, rplyNM,
opts, reqProcessor.Tenant,
da.cgrCfg.GeneralCfg().DefaultTenant,
utils.FirstNonEmpty(da.cgrCfg.DNSAgentCfg().Timezone,
da.cgrCfg.GeneralCfg().DefaultTimezone),
da.fltrS, nil),
utils.DNSAgent, da.connMgr,
da.cgrCfg.DNSAgentCfg().SessionSConns,
nil, da.fltrS); err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> error: %s processing message: %s from %s",
utils.DNSAgent, err.Error(), dnsDP, rmtAddr))
return
}
processed = processed || lclProcessed
if lclProcessed && !reqProcessor.Flags.GetBool(utils.MetaContinue) {
break
}
}
if !processed {
utils.Logger.Warning(
fmt.Sprintf("<%s> no request processor enabled, ignoring message %s from %s",
utils.DNSAgent, dnsDP, rmtAddr))
return
}
if err = updateDNSMsgFromNM(rply, rplyNM, q.Qtype, q.Name); err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> error: %s updating answer: %s from NM %s",
utils.DNSAgent, err.Error(), utils.ToJSON(rply), utils.ToJSON(rplyNM)))
}
return
}