feat: RFC-8427 (#171)
All checks were successful
continuous-integration/drone/push Build is passing

This PR will make the JSON response body be based off [RFC-8427](https://www.rfc-editor.org/rfc/rfc8427.html) which will be similar to [kdig's](https://www.knot-dns.cz/docs/2.6/html/man_kdig.html) JSON output.

Co-authored-by: Sam Therapy <sam@samtherapy.net>
Reviewed-on: #171
Reviewed-by: Sam Therapy <sam@samtherapy.net>
Co-authored-by: grumbulon <grumbulon@grumbulon.xyz>
Co-committed-by: grumbulon <grumbulon@grumbulon.xyz>
This commit is contained in:
grumbulon 2022-12-27 20:07:09 +00:00 committed by Sam Therapy
parent fdba9a0a41
commit d93eccc064
Signed by: Froth Git
GPG key ID: 5D8CD75CC6B79913
3 changed files with 387 additions and 330 deletions

View file

@ -3,7 +3,6 @@
package query
import (
"encoding/hex"
"encoding/json"
"encoding/xml"
"errors"
@ -250,297 +249,61 @@ func MakePrintable(res util.Response, opts *util.Options) (*Message, error) {
msg = res.DNS
)
// The things I do for compatibility
ret := Message{
Header: Header{
ID: msg.Id,
Response: msg.Response,
Opcode: dns.OpcodeToString[msg.Opcode],
Authoritative: msg.Authoritative,
Truncated: msg.Truncated,
RecursionDesired: msg.RecursionDesired,
RecursionAvailable: msg.RecursionAvailable,
Zero: msg.Zero,
AuthenticatedData: msg.AuthenticatedData,
CheckingDisabled: msg.CheckingDisabled,
Status: dns.RcodeToString[msg.Rcode],
},
ret := &Message{
DateString: time.Now().Format(time.RFC3339),
DateSeconds: time.Now().Unix(),
MsgSize: res.DNS.Len(),
ID: msg.Id,
Opcode: msg.Opcode,
Response: msg.Response,
Authoritative: msg.Authoritative,
Truncated: msg.Truncated,
RecursionDesired: msg.RecursionDesired,
RecursionAvailable: msg.RecursionAvailable,
AuthenticatedData: msg.AuthenticatedData,
CheckingDisabled: msg.CheckingDisabled,
Zero: msg.Zero,
QdCount: len(msg.Question),
AnCount: len(msg.Answer),
NsCount: len(msg.Ns),
ArCount: len(msg.Extra),
}
opt := msg.IsEdns0()
if opt != nil && opts.Display.Opt {
ret.Opt, err = parseOpt(*opt)
ret.EDNS0, err = ret.ParseOpt(msg.Rcode, *opt)
if err != nil {
return nil, fmt.Errorf("edns print: %w", err)
}
}
if opts.Display.Question {
for _, question := range msg.Question {
var name string
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(question.Name)
if err != nil {
return nil, fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = question.Name
}
ret.Question = append(ret.Question, Question{
Name: name,
Type: dns.TypeToString[question.Qtype],
Class: dns.ClassToString[question.Qclass],
})
err = ret.displayQuestion(msg, opts, opt)
if err != nil {
return nil, fmt.Errorf("unable to display questions: %w", err)
}
}
if opts.Display.Answer {
for _, answer := range msg.Answer {
temp := strings.Split(answer.String(), "\t")
var (
ttl any
name string
)
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
} else {
ttl = answer.Header().Ttl
}
}
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(answer.Header().Name)
if err != nil {
return nil, fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = answer.Header().Name
}
ret.Answer = append(ret.Answer, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[answer.Header().Rrtype],
Class: dns.ClassToString[answer.Header().Class],
Rdlength: answer.Header().Rdlength,
TTL: ttl,
},
Value: temp[len(temp)-1],
})
err = ret.displayAnswers(msg, opts, opt)
if err != nil {
return nil, fmt.Errorf("unable to display answers: %w", err)
}
}
if opts.Display.Authority {
for _, ns := range msg.Ns {
temp := strings.Split(ns.String(), "\t")
var (
ttl string
name string
)
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
} else {
ttl = strconv.Itoa(int(ns.Header().Ttl))
}
}
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(ns.Header().Name)
if err != nil {
return nil, fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = ns.Header().Name
}
ret.Authority = append(ret.Authority, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[ns.Header().Rrtype],
Class: dns.ClassToString[ns.Header().Class],
Rdlength: ns.Header().Rdlength,
TTL: ttl,
},
Value: temp[len(temp)-1],
})
err = ret.displayAuthority(msg, opts, opt)
if err != nil {
return nil, fmt.Errorf("unable to display authority: %w", err)
}
}
if opts.Display.Additional {
for _, additional := range msg.Extra {
if additional.Header().Rrtype == dns.StringToType["OPT"] {
continue
} else {
temp := strings.Split(additional.String(), "\t")
var (
ttl string
name string
)
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
} else {
ttl = strconv.Itoa(int(additional.Header().Ttl))
}
}
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(additional.Header().Name)
if err != nil {
return nil, fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = additional.Header().Name
}
ret.Additional = append(ret.Additional, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[additional.Header().Rrtype],
Class: dns.ClassToString[additional.Header().Class],
Rdlength: additional.Header().Rdlength,
TTL: ttl,
},
Value: temp[len(temp)-1],
})
}
}
}
if opts.Display.Statistics {
ret.Statistics = Statistics{
RTT: res.RTT.String(),
Server: opts.Request.Server + serverExtra(opts),
When: time.Now().Format(time.RFC3339),
MsgSize: res.DNS.Len(),
}
} else {
ret.Statistics = Statistics{}
}
return &ret, nil
}
func parseOpt(rr dns.OPT) ([]Opts, error) {
ret := []Opts{}
// Most of this is taken from https://github.com/miekg/dns/blob/master/edns.go#L76
ret = append(ret, Opts{
Name: "Version",
Value: strconv.Itoa(int(rr.Version())),
})
if rr.Do() {
ret = append(ret, Opts{
Name: "Flags",
Value: "do",
})
} else {
ret = append(ret, Opts{
Name: "Flags",
Value: "",
})
}
if rr.Hdr.Ttl&0x7FFF != 0 {
ret = append(ret, Opts{
Name: "MBZ",
Value: fmt.Sprintf("0x%04x", rr.Hdr.Ttl&0x7FFF),
})
}
ret = append(ret, Opts{
Name: "UDP Buffer Size",
Value: strconv.Itoa(int(rr.UDPSize())),
})
for _, opt := range rr.Option {
switch opt.(type) {
case *dns.EDNS0_NSID:
str := opt.String()
hex, err := hex.DecodeString(str)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
ret = append(ret, Opts{
Name: "NSID",
Value: fmt.Sprintf("%s (%s)", str, string(hex)),
})
case *dns.EDNS0_SUBNET:
ret = append(ret, Opts{
Name: "Subnet",
Value: opt.String(),
})
case *dns.EDNS0_COOKIE:
ret = append(ret, Opts{
Name: "Cookie",
Value: opt.String(),
})
case *dns.EDNS0_EXPIRE:
ret = append(ret, Opts{
Name: "Expire",
Value: opt.String(),
})
case *dns.EDNS0_TCP_KEEPALIVE:
ret = append(ret, Opts{
Name: "TCP Keepalive",
Value: opt.String(),
})
case *dns.EDNS0_UL:
ret = append(ret, Opts{
Name: "Update Lease",
Value: opt.String(),
})
case *dns.EDNS0_LLQ:
ret = append(ret, Opts{
Name: "Long Lived Queries",
Value: opt.String(),
})
case *dns.EDNS0_DAU:
ret = append(ret, Opts{
Name: "DNSSEC Algorithm Understood",
Value: opt.String(),
})
case *dns.EDNS0_DHU:
ret = append(ret, Opts{
Name: "DS Hash Understood",
Value: opt.String(),
})
case *dns.EDNS0_N3U:
ret = append(ret, Opts{
Name: "NSEC3 Hash Understood",
Value: opt.String(),
})
case *dns.EDNS0_LOCAL:
ret = append(ret, Opts{
Name: "Local OPT",
Value: opt.String(),
})
case *dns.EDNS0_PADDING:
ret = append(ret, Opts{
Name: "Padding",
Value: opt.String(),
})
case *dns.EDNS0_EDE:
ret = append(ret, Opts{
Name: "EDE",
Value: opt.String(),
})
case *dns.EDNS0_ESU:
ret = append(ret, Opts{
Name: "ESU",
Value: opt.String(),
})
err = ret.displayAdditional(msg, opts, opt)
if err != nil {
return nil, fmt.Errorf("unable to display additional: %w", err)
}
}

View file

@ -8,73 +8,109 @@ import (
// Message is for overall DNS responses.
//
//nolint:govet // Better looking output is worth a few bytes.
//nolint:govet,tagliatelle // Better looking output is worth a few bytes.
type Message struct {
// Header section
Header `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
// Opt Pseudosection
Opt []Opts `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,omitempty"`
// Question Section
Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:"question,omitempty"`
DateString string `json:"dateString,omitempty" xml:"dateString,omitempty" yaml:"dateString,omitempty"`
DateSeconds int64 `json:"dateSeconds,omitempty" xml:"dateSeconds,omitempty" yaml:"dateSeconds,omitempty"`
MsgSize int `json:"msgLength,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"`
ID uint16 `json:"ID" xml:"ID" yaml:"ID" example:"12"`
Opcode int `json:"opcode" xml:"opcode" yaml:"opcode" example:"QUERY"`
Response bool `json:"QR" xml:"QR" yaml:"QR" example:"true"`
Authoritative bool `json:"AA" xml:"AA" yaml:"AA" example:"false"`
Truncated bool `json:"TC" xml:"TC" yaml:"TC" example:"false"`
RecursionDesired bool `json:"RD" xml:"RD" yaml:"RD" example:"true"`
RecursionAvailable bool `json:"RA" xml:"RA" yaml:"RA" example:"true"`
AuthenticatedData bool `json:"AD" xml:"AD" yaml:"AD" example:"false"`
CheckingDisabled bool `json:"CD" xml:"CD" yaml:"CD" example:"false"`
Zero bool `json:"Z" xml:"Z" yaml:"Z" example:"false"`
QdCount int `json:"QDCOUNT" xml:"QDCOUNT" yaml:"QDCOUNT" example:"0"`
AnCount int `json:"ANCOUNT" xml:"ANCOUNT" yaml:"ANCOUNT" example:"0"`
NsCount int `json:"NSCOUNT" xml:"NSCOUNT" yaml:"NSCOUNT" example:"0"`
ArCount int `json:"ARCOUNT" xml:"ARCOUNT" yaml:"ARCOUNT" example:"0"`
Name string `json:"QNAME,omitempty" xml:"QNAME,omitempty" yaml:"QNAME,omitempty" example:"localhost"`
Type uint16 `json:"QTYPE,omitempty" xml:"QTYPE,omitempty" yaml:"QTYPE,omitempty" example:"IN"`
TypeName string `json:"QTYPEname,omitempty" xml:"QTYPEname,omitempty" yaml:"QTYPEname,omitempty" example:"IN"`
Class uint16 `json:"QCLASS,omitempty" xml:"QCLASS,omitempty" yaml:"QCLASS,omitempty" example:"A"`
ClassName string `json:"QCLASSname,omitempty" xml:"QCLASSname,omitempty" yaml:"QCLASSname,omitempty" example:"1"`
EDNS0 EDNS0 `json:",omitempty" xml:",omitempty" yaml:",omitempty"`
// Answer Section
Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:"answer,omitempty"`
// Authority Section
Authority []Answer `json:"authority,omitempty" xml:"authority,omitempty" yaml:"authority,omitempty"`
// Additional Section
Additional []Answer `json:"additional,omitempty" xml:"additional,omitempty" yaml:"additional,omitempty"`
// Statistics :)
Statistics `json:"statistics,omitempty" xml:"statistics,omitempty" yaml:"statistics,omitempty"`
AnswerRRs []Answer `json:"answersRRs,omitempty" xml:"answersRRs,omitempty" yaml:"answersRRs,omitempty" example:"false"`
AuthoritativeRRs []Answer `json:"authorityRRs,omitempty" xml:"authorityRRs,omitempty" yaml:"authorityRRs,omitempty" example:"false"`
AdditionalRRs []Answer `json:"additionalRRs,omitempty" xml:"additionalRRs,omitempty" yaml:"additionalRRs,omitempty" example:"false"`
}
// Header is the header.
type Header struct {
Opcode string `json:"opcode," xml:"opcode," yaml:"opcode" example:"QUERY"`
Status string `json:"status," xml:"status," yaml:"status" example:"NOERR"`
ID uint16 `json:"id," xml:"id," yaml:"id" example:"12"`
Response bool `json:"response," xml:"response," yaml:"response" example:"true"`
Authoritative bool `json:"authoritative," xml:"authoritative," yaml:"authoritative" example:"false"`
Truncated bool `json:"truncated," xml:"truncated," yaml:"truncated" example:"false"`
RecursionDesired bool `json:"recursionDesired," xml:"recursionDesired," yaml:"recursionDesired" example:"true"`
RecursionAvailable bool `json:"recursionAvailable," xml:"recursionAvailable," yaml:"recursionAvailable" example:"true"`
Zero bool `json:"zero," xml:"zero," yaml:"zero" example:"false"`
AuthenticatedData bool `json:"authenticatedData," xml:"authenticatedData," yaml:"authenticatedData" example:"false"`
CheckingDisabled bool `json:"checkingDisabled," xml:"checkingDisabled," yaml:"checkingDisabled" example:"false"`
}
// Question is a DNS Query.
type Question struct {
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"localhost"`
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
}
// RRHeader is for DNS Resource Headers.
type RRHeader struct {
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"127.0.0.1"`
TTL any `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:"ttl,omitempty" example:"0ms"`
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
}
// Opts is for the OPT pseudosection, nearly exclusively for EDNS.
type Opts struct {
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
Value string `json:"value" xml:"value" yaml:"value"`
}
// Answer is for a DNS Response.
// Answer is for DNS Resource Headers.
//
//nolint:govet,tagliatelle
type Answer struct {
Value string `json:"response,omitempty" xml:"response,omitempty" yaml:"response,omitempty"`
RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
Name string `json:"NAME,omitempty" xml:"NAME,omitempty" yaml:"NAME,omitempty" example:"127.0.0.1"`
Type uint16 `json:"TYPE,omitempty" xml:"TYPE,omitempty" yaml:"TYPE,omitempty" example:"1"`
TypeName string `json:"TYPEname,omitempty" xml:"TYPEname,omitempty" yaml:"TYPEname,omitempty" example:"A"`
Class uint16 `json:"CLASS,omitempty" xml:"CLASS,omitempty" yaml:"CLASS,omitempty" example:"1"`
ClassName string `json:"CLASSname,omitempty" xml:"CLASSname,omitempty" yaml:"CLASSname,omitempty" example:"IN"`
TTL any `json:"TTL,omitempty" xml:"TTL,omitempty" yaml:"TTL,omitempty" example:"0ms"`
Value string `json:"rdata,omitempty" xml:"rdata,omitempty" yaml:"rdata,omitempty"`
Rdlength uint16 `json:"RDLENGTH,omitempty" xml:"RDLENGTH,omitempty" yaml:"RDLENGTH,omitempty"`
Rdhex string `json:"RDATAHEX,omitempty" xml:"RDATAHEX,omitempty" yaml:"RDATAHEX,omitempty"`
}
// Statistics is the little bit at the bottom :).
type Statistics struct {
RTT string `json:"queryTime,omitempty" xml:"queryTime,omitempty" yaml:"queryTime,omitempty"`
Server string `json:"server,omitempty" xml:"server,omitempty" yaml:"server,omitempty"`
When string `json:"when,omitempty" xml:"when,omitempty" yaml:"when,omitempty"`
MsgSize int `json:"msgSize,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"`
// EDNS0 is for all EDNS options.
//
// RFC: https://datatracker.ietf.org/doc/draft-peltan-edns-presentation-format/
//
//nolint:govet,tagliatelle
type EDNS0 struct {
Flags []string `json:"FLAGS" xml:"FLAGS" yaml:"FLAGS"`
Rcode string `json:"RCODE" xml:"RCODE" yaml:"RCODE"`
PayloadSize uint16 `json:"UDPSIZE" xml:"UDPSIZE" yaml:"UDPSIZE"`
LLQ *EdnsLLQ `json:"LLQ,omitempty" xml:"LLQ,omitempty" yaml:"LLQ,omitempty"`
NsidHex string `json:"NSIDHEX,omitempty" xml:"NSIDHEX,omitempty" yaml:"NSIDHEX,omitempty"`
Nsid string `json:"NSID,omitempty" xml:"NSID,omitempty" yaml:"NSID,omitempty"`
Dau []uint8 `json:"DAU,omitempty" xml:"DAU,omitempty" yaml:"DAU,omitempty"`
Dhu []uint8 `json:"DHU,omitempty" xml:"DHU,omitempty" yaml:"DHU,omitempty"`
N3u []uint8 `json:"N3U,omitempty" xml:"N3U,omitempty" yaml:"N3U,omitempty"`
Subnet *EDNSSubnet `json:"ECS,omitempty" xml:"ECS,omitempty" yaml:"ECS,omitempty"`
Expire uint32 `json:"EXPIRE,omitempty" xml:"EXPIRE,omitempty" yaml:"EXPIRE,omitempty"`
Cookie []string `json:"COOKIE,omitempty" xml:"COOKIE,omitempty" yaml:"COOKIE,omitempty"`
KeepAlive uint16 `json:"KEEPALIVE,omitempty" xml:"KEEPALIVE,omitempty" yaml:"KEEPALIVE,omitempty"`
Padding string `json:"PADDING,omitempty" xml:"PADDING,omitempty" yaml:"PADDING,omitempty"`
Chain string `json:"CHAIN,omitempty" xml:"CHAIN,omitempty" yaml:"CHAIN,omitempty"`
EDE *EDNSErr `json:"EDE,omitempty" xml:"EDE,omitempty" yaml:"EDE,omitempty"`
}
// EdnsLLQ is for Long-lived queries.
//
//nolint:tagliatelle
type EdnsLLQ struct {
Version uint16 `json:"LLQ-VERSION" xml:"LLQ-VERSION" yaml:"LLQ-VERSION"`
Opcode uint16 `json:"LLQ-OPCODE" xml:"LLQ-OPCODE" yaml:"LLQ-OPCODE"`
Error uint16 `json:"LLQ-ERROR" xml:"LLQ-ERROR" yaml:"LLQ-ERROR"`
ID uint64 `json:"LLQ-ID" xml:"LLQ-ID" yaml:"LLQ-ID"`
Lease uint32 `json:"LLQ-LEASE" xml:"LLQ-LEASE" yaml:"LLQ-LEASE"`
}
// EDNSSubnet is for EDNS subnet options,
//
//nolint:govet,tagliatelle
type EDNSSubnet struct {
Family uint16 `json:"FAMILY" xml:"FAMILY" yaml:"FAMILY"`
IP string
Source uint8 `json:"SOURCE" xml:"SOURCE" yaml:"SOURCE"`
Scope uint8 `json:"SCOPE,omitempty" xml:"SCOPE,omitempty" yaml:"SCOPE,omitempty"`
}
// EDNSErr is for EDE codes
//
//nolint:govet,tagliatelle
type EDNSErr struct {
Code uint16 `json:"INFO-CODE" xml:"INFO-CODE" yaml:"INFO-CODE"`
Purpose string
Text string `json:"EXTRA-TEXT,omitempty" xml:"EXTRA-TEXT,omitempty" yaml:"EXTRA-TEXT,omitempty"`
}
var errNoMessage = errors.New("no message")

258
pkg/query/util.go Normal file
View file

@ -0,0 +1,258 @@
package query
import (
"encoding/hex"
"fmt"
"strings"
"time"
"git.froth.zone/sam/awl/pkg/util"
"github.com/miekg/dns"
"golang.org/x/net/idna"
)
func (message *Message) displayQuestion(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
var (
name string
err error
)
for _, question := range msg.Question {
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(question.Name)
if err != nil {
return fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = question.Name
}
message.Name = name
message.Type = question.Qtype
message.TypeName = dns.TypeToString[question.Qtype]
message.Class = question.Qclass
message.ClassName = dns.ClassToString[question.Qclass]
}
return nil
}
func (message *Message) displayAnswers(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
var (
ttl any
name string
err error
)
for _, answer := range msg.Answer {
temp := strings.Split(answer.String(), "\t")
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
} else {
ttl = answer.Header().Ttl
}
}
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(answer.Header().Name)
if err != nil {
return fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = answer.Header().Name
}
message.AnswerRRs = append(message.AnswerRRs, Answer{
Name: name,
ClassName: dns.ClassToString[answer.Header().Class],
Class: answer.Header().Class,
TypeName: dns.TypeToString[answer.Header().Rrtype],
Type: answer.Header().Rrtype,
Rdlength: answer.Header().Rdlength,
TTL: ttl,
Value: temp[len(temp)-1],
})
}
return nil
}
func (message *Message) displayAuthority(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
var (
ttl any
name string
err error
)
for _, ns := range msg.Ns {
temp := strings.Split(ns.String(), "\t")
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
} else {
ttl = ns.Header().Ttl
}
}
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(ns.Header().Name)
if err != nil {
return fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = ns.Header().Name
}
message.AuthoritativeRRs = append(message.AuthoritativeRRs, Answer{
Name: name,
TypeName: dns.TypeToString[ns.Header().Rrtype],
Type: ns.Header().Rrtype,
Class: ns.Header().Class,
ClassName: dns.ClassToString[ns.Header().Class],
Rdlength: ns.Header().Rdlength,
TTL: ttl,
Value: temp[len(temp)-1],
})
}
return nil
}
func (message *Message) displayAdditional(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
var (
ttl any
name string
err error
)
for _, additional := range msg.Extra {
if additional.Header().Rrtype == dns.StringToType["OPT"] {
continue
} else {
temp := strings.Split(additional.String(), "\t")
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
} else {
ttl = additional.Header().Ttl
}
}
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(additional.Header().Name)
if err != nil {
return fmt.Errorf("punycode to unicode: %w", err)
}
} else {
name = additional.Header().Name
}
message.AdditionalRRs = append(message.AdditionalRRs, Answer{
Name: name,
TypeName: dns.TypeToString[additional.Header().Rrtype],
Type: additional.Header().Rrtype,
Class: additional.Header().Class,
ClassName: dns.ClassToString[additional.Header().Class],
Rdlength: additional.Header().Rdlength,
TTL: ttl,
Value: temp[len(temp)-1],
})
}
}
return nil
}
// ParseOpt parses opts.
func (message *Message) ParseOpt(rcode int, rr dns.OPT) (ret EDNS0, err error) {
ret.Rcode = dns.RcodeToString[rcode]
// Most of this is taken from https://github.com/miekg/dns/blob/master/edns.go#L76
if rr.Do() {
ret.Flags = append(ret.Flags, "DO")
}
for i := uint32(1); i <= 0x7FFF; i <<= 1 {
if rr.Hdr.Ttl&i != 0 {
ret.Flags = append(ret.Flags, fmt.Sprintf("BIT%d", i))
}
}
ret.PayloadSize = rr.UDPSize()
for _, opt := range rr.Option {
switch opt := opt.(type) {
case *dns.EDNS0_NSID:
str := opt.String()
hex, err := hex.DecodeString(str)
if err != nil {
return ret, fmt.Errorf("%w", err)
}
ret.NsidHex = string(hex)
ret.Nsid = str
case *dns.EDNS0_SUBNET:
ret.Subnet = &EDNSSubnet{
Source: opt.SourceNetmask,
Family: opt.Family,
}
// 1: IPv4 2: IPv6
if ret.Subnet.Family <= 2 {
ret.Subnet.IP = opt.Address.String()
} else {
ret.Subnet.IP = hex.EncodeToString([]byte(opt.Address))
}
if opt.SourceScope != 0 {
ret.Subnet.Scope = opt.SourceScope
}
case *dns.EDNS0_COOKIE:
ret.Cookie = append(ret.Cookie, opt.String())
case *dns.EDNS0_EXPIRE:
ret.Expire = opt.Expire
case *dns.EDNS0_TCP_KEEPALIVE:
ret.KeepAlive = opt.Timeout
case *dns.EDNS0_LLQ:
ret.LLQ = &EdnsLLQ{
Version: opt.Version,
Opcode: opt.Opcode,
Error: opt.Error,
ID: opt.Id,
Lease: opt.LeaseLife,
}
case *dns.EDNS0_DAU:
ret.Dau = opt.AlgCode
case *dns.EDNS0_DHU:
ret.Dhu = opt.AlgCode
case *dns.EDNS0_N3U:
ret.N3u = opt.AlgCode
case *dns.EDNS0_PADDING:
ret.Padding = string(opt.Padding)
case *dns.EDNS0_EDE:
ret.EDE = &EDNSErr{
Code: opt.InfoCode,
Purpose: dns.ExtendedErrorCodeToString[opt.InfoCode],
Text: opt.ExtraText,
}
}
}
return ret, nil
}