From db763e071cc0ee17a2d1ff4e2b0c24671ff40981 Mon Sep 17 00:00:00 2001 From: Brian Cunnie Date: Sun, 10 Jul 2022 08:08:58 -0700 Subject: [PATCH] =?UTF-8?q?PTR:=201.0.0.127.in-addr.arpa=20=E2=86=92=20127?= =?UTF-8?q?.0.0.1.sslip.io?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We implement PTR records for IPv4. When a PTR record is not found (e.g. "127.in-addr.arpa"), it returns the SOA record, but, unlike other record lookups (e.g. "MX"), the SOA's mname is locked to "sslip.io" because setting the mname to "127.in-addr.arpa" doesn't make sense. To be done: - Implement IPv6 - Implement Metrics - Update README - Deploy new version --- src/sslip.io-dns-server/integration_test.go | 16 ++++++ src/sslip.io-dns-server/xip/xip.go | 63 +++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/sslip.io-dns-server/integration_test.go b/src/sslip.io-dns-server/integration_test.go index 297447e..fc48a32 100644 --- a/src/sslip.io-dns-server/integration_test.go +++ b/src/sslip.io-dns-server/integration_test.go @@ -173,6 +173,22 @@ var _ = Describe("sslip.io-dns-server", func() { "@127.0.0.1 get._acme-challenge.a.b.c.subdomain-key.k-v.io txt +short", `baffled-boy._acme-challenge`, `TypeTXT get._acme-challenge.a.b.c.subdomain-key.k-v.io. \? \["baffled-boy._acme-challenge"\]`), + Entry(`get a PTR for 1.0.168.192.in-addr.arpa returns 192.168.0.1.sslip.io`, + "@127.0.0.1 1.0.168.192.in-addr.arpa ptr +short", + `\A192.168.0.1.sslip.io.\n\z`, + `TypePTR 1.0.168.192.in-addr.arpa. \? 192.168.0.1.sslip.io.`), + Entry(`get a PTR for 1.0.0.127.blah.in-addr.arpa returns no records`, + "@127.0.0.1 1.0.0.127.blah.in-addr.arpa ptr +short", + `\A\z`, + `TypePTR 1.0.0.127.blah.in-addr.arpa. \? nil, SOA sslip.io. briancunnie.gmail.com. 2022042500 900 900 1800 180\n$`), + Entry(`get a PTR for blah.1.0.0.127.in-addr.arpa returns no records`, + "@127.0.0.1 blah.1.0.0.127.in-addr.arpa ptr +short", + `\A\z`, + `TypePTR blah.1.0.0.127.in-addr.arpa. \? nil, SOA sslip.io. briancunnie.gmail.com. 2022042500 900 900 1800 180\n$`), + Entry(`get a PTR for 0.0.127.in-addr.arpa returns no records`, + "@127.0.0.1 0.0.127.in-addr.arpa ptr +short", + `\A\z`, + `TypePTR 0.0.127.in-addr.arpa. \? nil, SOA sslip.io. briancunnie.gmail.com. 2022042500 900 900 1800 180\n$`), ) }) Describe("for more complex assertions", func() { diff --git a/src/sslip.io-dns-server/xip/xip.go b/src/sslip.io-dns-server/xip/xip.go index 3d455bf..16cb0e1 100644 --- a/src/sslip.io-dns-server/xip/xip.go +++ b/src/sslip.io-dns-server/xip/xip.go @@ -12,6 +12,7 @@ import ( "log" "net" "net/http" + "net/netip" "reflect" "regexp" "strconv" @@ -93,6 +94,7 @@ var ( ipv4REDashes = regexp.MustCompile(`(^|[.-])(((25[0-5]|(2[0-4]|1?[0-9])?[0-9])-){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))($|[.-])`) // https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses ipv6RE = regexp.MustCompile(`(^|[.-])(([0-9a-fA-F]{1,4}-){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,7}-|([0-9a-fA-F]{1,4}-){1,6}-[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,5}(-[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}-){1,4}(-[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}-){1,3}(-[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}-){1,2}(-[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}-((-[0-9a-fA-F]{1,4}){1,6})|-((-[0-9a-fA-F]{1,4}){1,7}|-)|fe80-(-[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|--(ffff(-0{1,4})?-)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}-){1,4}-((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))($|[.-])`) + ipv4ReverseRE = regexp.MustCompile(`^(.*)\.in-addr\.arpa\.$`) dns01ChallengeRE = regexp.MustCompile(`(?i)_acme-challenge\.`) // (?i) → non-capturing case insensitive kvRE = regexp.MustCompile(`\.k-v\.io\.$`) nsAwsSslip, _ = dnsmessage.NewName("ns-aws.sslip.io.") @@ -557,6 +559,40 @@ func (x *Xip) processQuestion(q dnsmessage.Question, srcAddr net.IP) (response R } return response, logMessage + strings.Join(logMessageTXTss, ", "), nil } + case dnsmessage.TypePTR: + { + var ptr *dnsmessage.PTRResource + ptr = PTRResource([]byte(q.Name.String())) + if ptr == nil { + // No Answers, only 1 Authorities + soaHeader, soaResource := SOAAuthority(dnsmessage.MustNewName("sslip.io.")) + response.Authorities = append(response.Authorities, + func(b *dnsmessage.Builder) error { + if err = b.SOAResource(soaHeader, soaResource); err != nil { + return err + } + return nil + }) + return response, logMessage + "nil, SOA " + soaLogMessage(soaResource), nil + } + //x.Metrics.AnsweredQueries++ + response.Answers = append(response.Answers, + // 1 CNAME record, via Customizations + func(b *dnsmessage.Builder) error { + err = b.PTRResource(dnsmessage.ResourceHeader{ + Name: q.Name, + Type: dnsmessage.TypePTR, + Class: dnsmessage.ClassINET, + TTL: 604800, // 60 * 60 * 24 * 7 == 1 week; long TTL, these IP addrs don't change + Length: 0, + }, *ptr) + if err != nil { + return err + } + return nil + }) + return response, logMessage + ptr.PTR.String(), nil + } default: { // default is the same case as an A/AAAA record which is not found, @@ -801,6 +837,33 @@ func SOAResource(name dnsmessage.Name) dnsmessage.SOAResource { } } +// PTRResource returns the PTR record, otherwise nil +func PTRResource(fqdn []byte) *dnsmessage.PTRResource { + // "reverse", for example, means "1.0.0.127", as in "1.0.0.127.in-addr.arpa" + // the regular IP would be "127.0.0.1" + if ipv4ReverseRE.Match(fqdn) { + reversedIPv4 := ipv4ReverseRE.FindSubmatch(fqdn)[1] + reversedIPv4address := net.ParseIP(string(reversedIPv4)).To4() + if reversedIPv4address == nil { + return nil + } + ip := netip.AddrFrom4([4]byte{ + reversedIPv4address[3], + reversedIPv4address[2], + reversedIPv4address[1], + reversedIPv4address[0], + }) + ptrName, err := dnsmessage.NewName(ip.String() + ".sslip.io.") + if err != nil { + return nil + } + return &dnsmessage.PTRResource{ + PTR: ptrName, + } + } + return nil +} + // when TXT for "ip.sslip.io" is queried, return the IP address of the querier func ipSslipIo(x *Xip, srcAddr net.IP) ([]dnsmessage.TXTResource, error) { x.Metrics.AnsweredTXTSrcIPQueries++