Files
cgrates/agents/libdns.go
2021-10-06 12:20:20 +02:00

718 lines
18 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 (
"fmt"
"net"
"strconv"
"strings"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"github.com/miekg/dns"
)
const (
QueryType = "QueryType"
QueryName = "QueryName"
dnsOption = "Option"
)
func newDnsReply(req *dns.Msg) (rply *dns.Msg) {
rply = new(dns.Msg)
rply.SetReply(req)
if len(req.Question) > 0 {
rply.Question = make([]dns.Question, len(req.Question))
for i, q := range req.Question {
rply.Question[i] = q
}
}
if opts := rply.IsEdns0(); opts != nil {
rply.SetEdns0(4096, false).IsEdns0().Option = opts.Option
}
return
}
// dnsWriteErr writes the error with code back to the client
func dnsWriteMsg(w dns.ResponseWriter, msg *dns.Msg) (err error) {
if err = w.WriteMsg(msg); err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> error: <%s> when writing on connection",
utils.DNSAgent, err.Error()))
}
return
}
func newDnsDP(req *dns.Msg) utils.DataProvider {
return &dnsDP{
req: config.NewObjectDP(req),
opts: config.NewObjectDP(req.IsEdns0()),
}
}
type dnsDP struct {
req utils.DataProvider
opts utils.DataProvider
}
func (dp dnsDP) String() string { return dp.req.String() }
func (dp dnsDP) FieldAsInterface(fldPath []string) (o interface{}, e error) {
if len(fldPath) != 0 && strings.HasPrefix(fldPath[0], dnsOption) {
return dp.opts.FieldAsInterface(fldPath)
}
return dp.req.FieldAsInterface(fldPath)
}
func (dp dnsDP) FieldAsString(fldPath []string) (string, error) {
valIface, err := dp.FieldAsInterface(fldPath)
if err != nil {
return "", err
}
return utils.IfaceAsString(valIface), nil
}
func updateDNSMsgFromNM(msg *dns.Msg, nm *utils.OrderedNavigableMap, qType uint16, qName string) (err error) {
msgFields := make(utils.StringSet) // work around to NMap issue
for el := nm.GetFirstElement(); el != nil; el = el.Next() {
path := el.Value
itm, _ := nm.Field(path)
switch path[0] { // go for each posible field
case utils.Id:
var vItm int64
if vItm, err = utils.IfaceAsTInt64(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Id = uint16(vItm)
case utils.Response:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Response = vItm
case utils.Opcode:
var vItm int64
if vItm, err = utils.IfaceAsTInt64(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Opcode = int(vItm)
case utils.Authoritative:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Authoritative = vItm
case utils.Truncated:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Truncated = vItm
case utils.RecursionDesired:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.RecursionDesired = vItm
case utils.RecursionAvailable:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.RecursionAvailable = vItm
case utils.Zero:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Zero = vItm
case utils.AuthenticatedData:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.AuthenticatedData = vItm
case utils.CheckingDisabled:
var vItm bool
if vItm, err = utils.IfaceAsBool(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.CheckingDisabled = vItm
case utils.Rcode:
var vItm int64
if vItm, err = utils.IfaceAsTInt64(itm.Data); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[0], err.Error())
}
msg.Rcode = int(vItm)
case utils.Question:
if msg.Question, err = updateDnsQuestions(msg.Question, path[1:len(path)-1], itm.Data, itm.NewBranch); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[:len(path)-1], err.Error())
}
case utils.Answer:
newBranch := itm.NewBranch ||
len(msg.Answer) == 0 ||
msgFields.Has(path[0])
if newBranch { // force append if the same path was already used
msgFields = make(utils.StringSet) // reset the fields inside since we have a new message
msgFields.Add(strings.Join(path, ".")) // detect new branch
}
if msg.Answer, err = updateDnsAnswer(msg.Answer, qType, qName, path[1:len(path)-1], itm.Data, newBranch); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[:len(path)-1], err.Error())
}
case utils.Ns: //ToDO
case utils.Extra: //ToDO
case dnsOption:
opts := msg.IsEdns0()
if opts == nil {
opts = msg.SetEdns0(4096, false).IsEdns0()
}
if opts.Option, err = updateDnsOption(opts.Option, path[1:len(path)-1], itm.Data, itm.NewBranch); err != nil {
return fmt.Errorf("item: <%s>, err: %s", path[:len(path)-1], err.Error())
}
default:
}
}
return
}
// updateDnsQuestion
func updateDnsQuestions(q []dns.Question, path []string, value interface{}, newBranch bool) (_ []dns.Question, err error) {
var idx int
var field string
switch len(path) {
case 1: // only the field so update the last one
if newBranch || len(q) == 0 {
q = append(q, dns.Question{})
}
idx = len(q) - 1
field = path[0]
case 2: // the index is specified
if idx, err = strconv.Atoi(path[0]); err != nil {
return
}
if lq := len(q); idx > lq {
err = utils.ErrWrongPath
return
} else if lq == idx {
q = append(q, dns.Question{})
}
field = path[1]
default:
err = utils.ErrWrongPath
return
}
switch field {
case utils.Name:
q[idx].Name = utils.IfaceAsString(value)
case utils.Qtype:
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
q[idx].Qtype = uint16(vItm)
case utils.Qclass:
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
q[idx].Qclass = uint16(vItm)
default:
err = utils.ErrWrongPath
return
}
return q, nil
}
func updateDnsOption(q []dns.EDNS0, path []string, value interface{}, newBranch bool) (_ []dns.EDNS0, err error) {
var idx int
var field string
switch len(path) {
case 1: // only the field so update the last one
field = path[0]
if newBranch ||
len(q) == 0 {
var o dns.EDNS0
if o, err = createDnsOption(field, value); err != nil {
return
}
return append(q, o), nil
}
idx = len(q) - 1
case 2: // the index is specified
field = path[1]
if idx, err = strconv.Atoi(path[0]); err != nil {
return
}
if lq := len(q); idx > lq {
err = utils.ErrWrongPath
return
} else if lq == idx {
var o dns.EDNS0
if o, err = createDnsOption(field, value); err != nil {
return
}
return append(q, o), nil
}
default:
err = utils.ErrWrongPath
return
}
switch v := q[idx].(type) {
case *dns.EDNS0_NSID:
if field != "Nsid" {
err = utils.ErrWrongPath
return
}
v.Nsid = utils.IfaceAsString(value)
case *dns.EDNS0_SUBNET:
switch field {
case "Family":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Family = uint16(vItm)
case "SourceNetmask":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.SourceNetmask = uint8(vItm)
case "SourceScope":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.SourceScope = uint8(vItm)
case "Address":
v.Address = net.ParseIP(utils.IfaceAsString(value))
default:
err = utils.ErrWrongPath
return
}
case *dns.EDNS0_COOKIE:
if field != "Cookie" {
err = utils.ErrWrongPath
return
}
v.Cookie = utils.IfaceAsString(value)
case *dns.EDNS0_UL:
switch field {
case "Lease":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Lease = uint32(vItm)
case "KeyLease":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.KeyLease = uint32(vItm)
default:
err = utils.ErrWrongPath
return
}
case *dns.EDNS0_LLQ:
switch field {
case "Version":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Version = uint16(vItm)
case "Opcode":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Opcode = uint16(vItm)
case "Error":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Error = uint16(vItm)
case "Id":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Id = uint64(vItm)
case "LeaseLife":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.LeaseLife = uint32(vItm)
default:
err = utils.ErrWrongPath
return
}
case *dns.EDNS0_DAU:
if field != "DAU" {
err = utils.ErrWrongPath
return
}
v.AlgCode = []uint8(utils.IfaceAsString(value))
case *dns.EDNS0_DHU:
if field != "DHU" {
err = utils.ErrWrongPath
return
}
v.AlgCode = []uint8(utils.IfaceAsString(value))
case *dns.EDNS0_N3U:
if field != "N3U" {
err = utils.ErrWrongPath
return
}
v.AlgCode = []uint8(utils.IfaceAsString(value))
case *dns.EDNS0_EXPIRE:
if field != "Expire" {
err = utils.ErrWrongPath
return
}
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Expire = uint32(vItm)
case *dns.EDNS0_TCP_KEEPALIVE:
switch field {
case "Length": //
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Length = uint16(vItm)
case "Timeout": //
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Timeout = uint16(vItm)
default:
err = utils.ErrWrongPath
return
}
case *dns.EDNS0_PADDING:
if field != "Padding" {
err = utils.ErrWrongPath
return
}
v.Padding = []byte(utils.IfaceAsString(value))
case *dns.EDNS0_EDE:
switch field {
case "InfoCode":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.InfoCode = uint16(vItm)
case "ExtraText":
v.ExtraText = utils.IfaceAsString(value)
default:
err = utils.ErrWrongPath
return
}
case *dns.EDNS0_ESU:
if field != "Uri" {
err = utils.ErrWrongPath
return
}
v.Uri = utils.IfaceAsString(value)
case *dns.EDNS0_LOCAL: // if already there you can change data
if field != "Data" {
err = utils.ErrWrongPath
return
}
v.Data = []byte(utils.IfaceAsString(value))
case nil:
err = fmt.Errorf("unsupported dns option type <%T>", v)
default:
err = fmt.Errorf("unsupported dns option type <%T>", v)
}
return q, err
}
func createDnsOption(field string, value interface{}) (o dns.EDNS0, err error) {
switch field {
case "Nsid": // EDNS0_NSID
o = &dns.EDNS0_NSID{Nsid: utils.IfaceAsString(value)}
case "Family": // EDNS0_SUBNET
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_SUBNET{Family: uint16(vItm)}
case "SourceNetmask": // EDNS0_SUBNET
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_SUBNET{SourceNetmask: uint8(vItm)}
case "SourceScope": // EDNS0_SUBNET
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_SUBNET{SourceScope: uint8(vItm)}
case "Address": // EDNS0_SUBNET
o = &dns.EDNS0_SUBNET{Address: net.ParseIP(utils.IfaceAsString(value))}
case "Cookie": // EDNS0_COOKIE
o = &dns.EDNS0_COOKIE{Cookie: utils.IfaceAsString(value)}
case "Lease": // EDNS0_UL
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_UL{Lease: uint32(vItm)}
case "KeyLease": // EDNS0_UL
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_UL{KeyLease: uint32(vItm)}
case "Version": // EDNS0_LLQ
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_LLQ{Version: uint16(vItm)}
case "Opcode": // EDNS0_LLQ
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_LLQ{Opcode: uint16(vItm)}
case "Error": // EDNS0_LLQ
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_LLQ{Error: uint16(vItm)}
case "Id": // EDNS0_LLQ
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_LLQ{Id: uint64(vItm)}
case "LeaseLife": // EDNS0_LLQ
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_LLQ{LeaseLife: uint32(vItm)}
case "DAU": // EDNS0_DAU
o = &dns.EDNS0_DAU{AlgCode: []uint8(utils.IfaceAsString(value))}
case "DHU": // EDNS0_DHU
o = &dns.EDNS0_DHU{AlgCode: []uint8(utils.IfaceAsString(value))}
case "N3U": // EDNS0_N3U
o = &dns.EDNS0_N3U{AlgCode: []uint8(utils.IfaceAsString(value))}
case "Expire": // EDNS0_EXPIRE
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_EXPIRE{Expire: uint32(vItm)}
case "Length": // EDNS0_TCP_KEEPALIVE
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_TCP_KEEPALIVE{Length: uint16(vItm)}
case "Timeout": // EDNS0_TCP_KEEPALIVE
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_TCP_KEEPALIVE{Timeout: uint16(vItm)}
case "Padding": // EDNS0_PADDING
o = &dns.EDNS0_PADDING{Padding: []byte(utils.IfaceAsString(value))}
case "InfoCode": // EDNS0_EDE
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
o = &dns.EDNS0_EDE{InfoCode: uint16(vItm)}
case "ExtraText": // EDNS0_EDE
o = &dns.EDNS0_EDE{ExtraText: utils.IfaceAsString(value)}
case "Uri": // EDNS0_ESU
o = &dns.EDNS0_ESU{Uri: utils.IfaceAsString(value)}
default:
err = fmt.Errorf("can not create option from field <%q>", field)
}
return
}
func updateDnsAnswer(q []dns.RR, qType uint16, qName string, path []string, value interface{}, newBranch bool) (_ []dns.RR, err error) {
var idx int
if lPath := len(path); lPath == 0 {
err = utils.ErrWrongPath
return
} else {
var hasIdx bool
if idx, err = strconv.Atoi(path[0]); err == nil {
hasIdx = true
}
err = nil
if !hasIdx || lPath == 1 { // only the field so update the last one
if newBranch || len(q) == 0 {
var a dns.RR
if a, err = newDNSAnswer(qType, qName); err != nil {
return
}
q = append(q, a)
}
idx = len(q) - 1
} else { // the index is specified
if lq := len(q); idx > lq {
err = utils.ErrWrongPath
return
} else if lq == idx {
var a dns.RR
if a, err = newDNSAnswer(qType, qName); err != nil {
return
}
q = append(q, a)
}
path = path[1:]
}
}
switch v := q[idx].(type) {
case *dns.NAPTR:
err = updateDnsNAPTRAnswer(v, path, value)
case *dns.A:
if len(path) < 1 ||
(path[0] != "Hdr" && len(path) != 1) ||
(path[0] == "Hdr" && len(path) != 2) {
err = utils.ErrWrongPath
return
}
switch path[0] {
case "Hdr":
err = updateDnsRRHeader(&v.Hdr, path[1:], value)
case "A":
v.A = net.IP(utils.IfaceAsString(value))
default:
err = utils.ErrWrongPath
}
case nil:
err = fmt.Errorf("unsupported dns option type <%T>", v)
default:
err = fmt.Errorf("unsupported dns option type <%T>", v)
}
return q, err
}
// appendDNSAnswer will append the right answer payload to the message
func newDNSAnswer(qType uint16, qName string) (a dns.RR, err error) {
hdr := dns.RR_Header{
Name: qName,
Rrtype: qType,
Class: dns.ClassINET,
Ttl: 60,
}
switch qType {
case dns.TypeA:
a = &dns.A{Hdr: hdr}
case dns.TypeNAPTR:
a = &dns.NAPTR{Hdr: hdr}
default:
err = fmt.Errorf("unsupported DNS type: <%v>", dns.TypeToString[qType])
}
return
}
func updateDnsNAPTRAnswer(v *dns.NAPTR, path []string, value interface{}) (err error) {
if len(path) < 1 ||
(path[0] != "Hdr" && len(path) != 1) ||
(path[0] == "Hdr" && len(path) != 2) {
return utils.ErrWrongPath
}
switch path[0] {
case "Hdr":
return updateDnsRRHeader(&v.Hdr, path[1:], value)
case "Order":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Order = uint16(vItm)
case "Preference":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Preference = uint16(vItm)
case "Flags":
v.Flags = utils.IfaceAsString(value)
case "Service":
v.Service = utils.IfaceAsString(value)
case "Regexp":
v.Regexp = utils.IfaceAsString(value)
case "Replacement":
v.Replacement = utils.IfaceAsString(value)
default:
return utils.ErrWrongPath
}
return
}
func updateDnsRRHeader(v *dns.RR_Header, path []string, value interface{}) (err error) {
if len(path) != 1 {
return utils.ErrWrongPath
}
switch path[0] {
case "Name":
v.Name = utils.IfaceAsString(value)
case "Rrtype":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Rrtype = uint16(vItm)
case "Class":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Class = uint16(vItm)
case "Ttl":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Ttl = uint32(vItm)
case "Rdlength":
var vItm int64
if vItm, err = utils.IfaceAsTInt64(value); err != nil {
return
}
v.Rdlength = uint16(vItm)
default:
return utils.ErrWrongPath
}
return
}