Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add structures for EDNS and NSID option #342

Merged
merged 5 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/alookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Specifically, alookup acts similar to nslookup and will follow CNAME records.`,
&Timeout, &IterationTimeout,
&Class_string, &Servers_string,
&Config_file, &Localaddr_string,
&Localif_string, &NanoSeconds, &ClientSubnet_string)
&Localif_string, &NanoSeconds, &ClientSubnet_string, &NSID)
},
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/mxlookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ correspond with an exchange record.`,
&Timeout, &IterationTimeout,
&Class_string, &Servers_string,
&Config_file, &Localaddr_string,
&Localif_string, &NanoSeconds, &ClientSubnet_string)
&Localif_string, &NanoSeconds, &ClientSubnet_string, &NSID)
},
}

Expand Down
4 changes: 3 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var (
Class_string string
NanoSeconds bool
ClientSubnet_string string
NSID bool
)

// rootCmd represents the base command when called without any subcommands
Expand All @@ -57,7 +58,7 @@ ZDNS also includes its own recursive resolution and a cache to further optimize
&Timeout, &IterationTimeout,
&Class_string, &Servers_string,
&Config_file, &Localaddr_string,
&Localif_string, &NanoSeconds, &ClientSubnet_string)
&Localif_string, &NanoSeconds, &ClientSubnet_string, &NSID)
},
}

Expand Down Expand Up @@ -117,6 +118,7 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&NanoSeconds, "nanoseconds", false, "Use nanosecond resolution timestamps")
rootCmd.PersistentFlags().StringVar(&ClientSubnet_string, "client-subnet", "", "Client subnet in CIDR format for EDNS0.")
rootCmd.PersistentFlags().BoolVar(&GC.Dnssec, "dnssec", false, "Requests DNSSEC records by setting the DNSSEC OK (DO) bit")
rootCmd.PersistentFlags().BoolVar(&NSID, "nsid", false, "Request NSID.")

rootCmd.PersistentFlags().Bool("ipv4-lookup", false, "Perform an IPv4 Lookup in modules")
rootCmd.PersistentFlags().Bool("ipv6-lookup", false, "Perform an IPv6 Lookup in modules")
Expand Down
43 changes: 42 additions & 1 deletion integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import unittest
import tempfile
from ipaddress import ip_address


def recursiveSort(obj):
Expand Down Expand Up @@ -282,7 +283,7 @@ def run_zdns_multiline(self, flags, names, executable=ZDNS_EXECUTABLE):
}
}
}

TCP_LARGE_TXT_ANSWERS = [
{
"type": "TXT",
Expand Down Expand Up @@ -890,15 +891,55 @@ def test_edns0_client_subnet(self):
c = f"A --client-subnet {subnet} --name-servers=8.8.8.8:53"
cmd, res = self.run_zdns(c, name)
self.assertSuccess(res, cmd)
address, netmask = tuple(subnet.split("/"))
family = 1 if ip_address(address).version == 4 else 2
self.assertEqual(address, res["data"]['additionals'][0]['csubnet']['address'])
self.assertEqual(int(netmask), res["data"]['additionals'][0]['csubnet']["source_netmask"])
self.assertEqual(family, res["data"]['additionals'][0]['csubnet']['family'])
self.assertTrue("source_scope" in res["data"]['additionals'][0]['csubnet'])
correct = [{"type":"A", "class":"IN", "answer": ip_addr, "name":"ecs-geo.zdns-testing.com"}]
self.assertEqualAnswers(res, correct, cmd)

def test_edns0_nsid(self):
name = "google.com"
# using Google Public DNS for testing as its NSID always follows format 'gpdns-<airport code>'
c = f"A --nsid --name-servers=8.8.8.8:53"
cmd, res = self.run_zdns(c, name)
self.assertSuccess(res, cmd)
self.assertTrue("nsid" in res["data"]['additionals'][0])
self.assertTrue(res["data"]['additionals'][0]['nsid']['nsid'].startswith("gpdns-"))

def test_edns0_ede_1(self):
name = "dnssec.fail"
# using Cloudflare Public DNS (1.1.1.1) that implements EDE
c = f"A --name-servers=1.1.1.1:53"
cmd, res = self.run_zdns(c, name)
self.assertServFail(res, cmd)
self.assertTrue("ede" in res["data"]['additionals'][0])
ede_obj = res["data"]['additionals'][0]["ede"][0]
self.assertEqual("DNSKEY Missing", ede_obj["error_text"])
self.assertEqual("no SEP matching the DS found for dnssec.fail.", ede_obj["extra_text"])
self.assertEqual(9, ede_obj["info_code"])

def test_edns0_ede_2_cd(self):
name = "dnssec.fail"
# using Cloudflare Public DNS (1.1.1.1) that implements EDE, checking disabled resulting in NOERROR
c = f"A --name-servers=1.1.1.1:53 --checking-disabled"
cmd, res = self.run_zdns(c, name)
self.assertSuccess(res, cmd)
self.assertTrue("ede" in res["data"]['additionals'][0])
ede_obj = res["data"]['additionals'][0]["ede"][0]
self.assertEqual("DNSKEY Missing", ede_obj["error_text"])
self.assertEqual("no SEP matching the DS found for dnssec.fail.", ede_obj["extra_text"])
self.assertEqual(9, ede_obj["info_code"])

def test_dnssec_response(self):
# checks if dnssec records are returned
c = f"SOA --dnssec --name-servers=8.8.8.8:53"
name = "."
cmd, res = self.run_zdns(c, name)
self.assertSuccess(res, cmd)
self.assertEqual('do', res["data"]['additionals'][0]['flags'])
self.assertEqualTypes(res, ["SOA", "RRSIG"])

def test_cd_bit_not_set(self):
Expand Down
88 changes: 76 additions & 12 deletions pkg/miekg/answers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package miekg

import (
"encoding/hex"
"fmt"
"net"
"strconv"
Expand Down Expand Up @@ -72,13 +73,6 @@ type DSAnswer struct {
Digest string `json:"digest" groups:"short,normal,long,trace"`
}

type EDNSAnswer struct {
Type string `json:"type" groups:"short,normal,long,trace"`
Version uint8 `json:"version" groups:"short,normal,long,trace"`
Flags string `json:"flags" groups:"short,normal,long,trace"`
UDPSize uint16 `json:"udpsize" groups:"short,normal,long,trace"`
}

type GPOSAnswer struct {
Answer
Longitude string `json:"preference" groups:"short,normal,long,trace"`
Expand Down Expand Up @@ -466,15 +460,85 @@ func makeEDNSAnswer(cAns *dns.OPT) EDNSAnswer {
if cAns.Do() {
flags = "do"
}
return EDNSAnswer{
Type: opttype + strconv.Itoa(int(cAns.Version())),
Version: cAns.Version(),
optRes := EDNSAnswer{
Type: opttype + strconv.Itoa(int(cAns.Version())),
Version: cAns.Version(),
// RCODE omitted for now as no EDNS0 extension is supported in
// lookups for which an RCODE is defined.
//Rcode: dns.RcodeToString[cAns.ExtendedRcode()],
Flags: flags,
UDPSize: cAns.UDPSize(),
Flags: flags,
UDPSize: cAns.UDPSize(),
}

for _, o := range cAns.Option {
switch o.(type) {
case *dns.EDNS0_LLQ: //OPT 1
optRes.LLQ = &Edns0LLQ{
Code: o.(*dns.EDNS0_LLQ).Code,
Version: o.(*dns.EDNS0_LLQ).Version,
Opcode: o.(*dns.EDNS0_LLQ).Opcode,
Error: o.(*dns.EDNS0_LLQ).Error,
Id: o.(*dns.EDNS0_LLQ).Id,
LeaseLife: o.(*dns.EDNS0_LLQ).LeaseLife,
}
case *dns.EDNS0_UL: // OPT 2
optRes.UL = &Edns0UL{
Code: o.(*dns.EDNS0_UL).Code,
Lease: o.(*dns.EDNS0_UL).Lease,
KeyLease: o.(*dns.EDNS0_UL).KeyLease,
}
case *dns.EDNS0_NSID: //OPT 3
hexDecoded, err := hex.DecodeString(o.(*dns.EDNS0_NSID).Nsid)
if err != nil {
continue
}
optRes.NSID = &Edns0NSID{Nsid: string(hexDecoded)}
case *dns.EDNS0_DAU: //OPT 5
optRes.DAU = &Edns0DAU{
Code: o.(*dns.EDNS0_DAU).Code,
AlgCode: o.(*dns.EDNS0_DAU).String(),
}
case *dns.EDNS0_DHU: //OPT 6
optRes.DHU = &Edns0DHU{
Code: o.(*dns.EDNS0_DHU).Code,
AlgCode: o.(*dns.EDNS0_DHU).String(),
}
case *dns.EDNS0_N3U: //OPT 7
optRes.N3U = &Edns0N3U{
Code: o.(*dns.EDNS0_N3U).Code,
AlgCode: o.(*dns.EDNS0_N3U).String(),
}
case *dns.EDNS0_SUBNET: //OPT 8
optRes.ClientSubnet = &Edns0ClientSubnet{
SourceScope: o.(*dns.EDNS0_SUBNET).SourceScope,
Family: o.(*dns.EDNS0_SUBNET).Family,
Address: o.(*dns.EDNS0_SUBNET).Address.String(),
SourceNetmask: o.(*dns.EDNS0_SUBNET).SourceNetmask,
}
case *dns.EDNS0_EXPIRE: //OPT 9
optRes.Expire = &Edns0Expire{
Code: o.(*dns.EDNS0_EXPIRE).Code,
Expire: o.(*dns.EDNS0_EXPIRE).Expire,
}
case *dns.EDNS0_COOKIE: //OPT 11
optRes.Cookie = &Edns0Cookie{Cookie: o.(*dns.EDNS0_COOKIE).Cookie}
case *dns.EDNS0_TCP_KEEPALIVE: //OPT 11
optRes.TcpKeepalive = &Edns0TCPKeepalive{
Code: o.(*dns.EDNS0_TCP_KEEPALIVE).Code,
Timeout: o.(*dns.EDNS0_TCP_KEEPALIVE).Timeout,
Length: o.(*dns.EDNS0_TCP_KEEPALIVE).Length, // deprecated, always equal to 0, keeping it here for a better readability
}
case *dns.EDNS0_PADDING: //OPT 12
optRes.Padding = &Edns0Padding{Padding: o.(*dns.EDNS0_PADDING).String()}
case *dns.EDNS0_EDE: //OPT 15
optRes.EDE = append(optRes.EDE, &Edns0Ede{
InfoCode: o.(*dns.EDNS0_EDE).InfoCode,
ErrorCodeText: dns.ExtendedErrorCodeToString[o.(*dns.EDNS0_EDE).InfoCode],
ExtraText: o.(*dns.EDNS0_EDE).ExtraText,
})
}
}
return optRes
}

func ParseAnswer(ans dns.RR) interface{} {
Expand Down
114 changes: 114 additions & 0 deletions pkg/miekg/edns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* ZDNS Copyright 2023 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package miekg

// Structures covering DNS EDNS0 Option Codes (https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11)

// Edns0LLQ OPT 1
type Edns0LLQ struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
Version uint16 `json:"version" groups:"short,normal,long,trace"`
Opcode uint16 `json:"opcode" groups:"short,normal,long,trace"`
Error uint16 `json:"error" groups:"short,normal,long,trace"`
Id uint64 `json:"id" groups:"short,normal,long,trace"`
LeaseLife uint32 `json:"lease_life" groups:"short,normal,long,trace"`
}

// Edns0UL OPT 2
type Edns0UL struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
Lease uint32 `json:"lease" groups:"short,normal,long,trace"`
KeyLease uint32 `json:"key_lease" groups:"short,normal,long,trace"`
}

// Edns0NSID OPT 3
type Edns0NSID struct {
Nsid string `json:"nsid" groups:"short,normal,long,trace"`
}

// Edns0DAU OPT 5
type Edns0DAU struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
AlgCode string `json:"alg_code" groups:"short,normal,long,trace"`
}

// Edns0DHU OPT 6
type Edns0DHU struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
AlgCode string `json:"alg_code" groups:"short,normal,long,trace"`
}

// Edns0N3U OPT 7
type Edns0N3U struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
AlgCode string `json:"alg_code" groups:"short,normal,long,trace"`
}

// Edns0ClientSubnet OPT 8
type Edns0ClientSubnet struct {
Family uint16 `json:"family" groups:"short,normal,long,trace"`
SourceNetmask uint8 `json:"source_netmask" groups:"short,normal,long,trace"`
SourceScope uint8 `json:"source_scope" groups:"short,normal,long,trace"`
Address string `json:"address" groups:"short,normal,long,trace"`
}

// Edns0Expire OPT 9
type Edns0Expire struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
Expire uint32 `json:"expire" groups:"short,normal,long,trace"`
}

// Edns0Cookie OPT 10
type Edns0Cookie struct {
Cookie string `json:"cookie" groups:"short,normal,long,trace"`
}

// Edns0TCPKeepalive OPT 11
type Edns0TCPKeepalive struct {
Code uint16 `json:"code" groups:"short,normal,long,trace"`
Timeout uint16 `json:"timeout" groups:"short,normal,long,trace"`
Length uint16 `json:"length" groups:"short,normal,long,trace"`
}

// Edns0Padding OPT 12
type Edns0Padding struct {
Padding string `json:"padding" groups:"short,normal,long,trace"`
}

// Edns0Ede OPT15
type Edns0Ede struct {
InfoCode uint16 `json:"info_code" groups:"short,normal,long,trace"`
ErrorCodeText string `json:"error_text" groups:"short,normal,long,trace"`
ExtraText string `json:"extra_text" groups:"short,normal,long,trace"`
}

type EDNSAnswer struct {
Type string `json:"type" groups:"short,normal,long,trace"`
Version uint8 `json:"version" groups:"short,normal,long,trace"`
Flags string `json:"flags" groups:"short,normal,long,trace"`
UDPSize uint16 `json:"udpsize" groups:"short,normal,long,trace"`
LLQ *Edns0LLQ `json:"llq,omitempty" groups:"short,normal,long,trace"` //not implemented
UL *Edns0UL `json:"ul,omitempty" groups:"short,normal,long,trace"` //not implemented
NSID *Edns0NSID `json:"nsid,omitempty" groups:"short,normal,long,trace"`
DAU *Edns0DAU `json:"dau,omitempty" groups:"short,normal,long,trace"` //not implemented
DHU *Edns0DHU `json:"dhu,omitempty" groups:"short,normal,long,trace"` //not implemented
N3U *Edns0N3U `json:"n3u,omitempty" groups:"short,normal,long,trace"` //not implemented
ClientSubnet *Edns0ClientSubnet `json:"csubnet,omitempty" groups:"short,normal,long,trace"`
Expire *Edns0Expire `json:"expire,omitempty" groups:"short,normal,long,trace"` //not implemented
Cookie *Edns0Cookie `json:"cookie,omitempty" groups:"short,normal,long,trace"` //not implemented
TcpKeepalive *Edns0TCPKeepalive `json:"tcp_keepalive,omitempty" groups:"short,normal,long,trace"` //not implemented
Padding *Edns0Padding `json:"padding,omitempty" groups:"short,normal,long,trace"` //not implemented
EDE []*Edns0Ede `json:"ede,omitempty" groups:"short,normal,long,trace"`
}
Loading