diff --git a/integration_flags_test.go b/integration_flags_test.go index c38290d..846b7b3 100644 --- a/integration_flags_test.go +++ b/integration_flags_test.go @@ -136,6 +136,22 @@ var _ = Describe("flags", func() { Eventually(digSession, 1).Should(Exit(0)) Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`\? nil, SOA 8-8-8-8\.sslip\.io\. briancunnie\.gmail\.com\.`)) }) + It("doesn't resolve public IPv4 addresses (hexadecimal)", func() { + digArgs := "@localhost 08080808.sslip.io -p " + strconv.Itoa(port) + digCmd := exec.Command("dig", strings.Split(digArgs, " ")...) + digSession, err := Start(digCmd, GinkgoWriter, GinkgoWriter) + Expect(err).ToNot(HaveOccurred()) + Eventually(digSession, 1).Should(Exit(0)) + Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`\? nil, SOA 08080808\.sslip\.io\. briancunnie\.gmail\.com\.`)) + }) + It("resolves private IPv4 addresses (hexadecimal)", func() { + digArgs := "@localhost 7f000001.sslip.io -p " + strconv.Itoa(port) + digCmd := exec.Command("dig", strings.Split(digArgs, " ")...) + digSession, err := Start(digCmd, GinkgoWriter, GinkgoWriter) + Expect(err).ToNot(HaveOccurred()) + Eventually(digSession, 1).Should(Exit(0)) + Eventually(string(serverSession.Err.Contents())).Should(MatchRegexp(`TypeA 7f000001.sslip.io. \? 127.0.0.1`)) + }) It("doesn't resolve public IPv6 addresses", func() { digArgs := "@localhost aaaa 2600--.sslip.io -p " + strconv.Itoa(port) digCmd := exec.Command("dig", strings.Split(digArgs, " ")...) diff --git a/xip/xip.go b/xip/xip.go index 8d0b4ad..8976638 100644 --- a/xip/xip.go +++ b/xip/xip.go @@ -5,6 +5,7 @@ package xip import ( "bufio" + "encoding/hex" "errors" "fmt" "io" @@ -83,21 +84,22 @@ type DomainCustomizations map[string]DomainCustomization var ( ipv4REDots = regexp.MustCompile(`(^|[.-])(((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))($|[.-])`) ipv4REDashes = regexp.MustCompile(`(^|[.-])(((25[0-5]|(2[0-4]|1?\d)?\d)-){3}(25[0-5]|(2[0-4]|1?\d)?\d))($|[.-])`) + ipv4REHex = regexp.MustCompile(`(^|\.)([[:xdigit:]]{8})($|\.)`) // no dash separators, only dots // https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses ipv6RE = regexp.MustCompile(`(^|[.-])(([[:xdigit:]]{1,4}-){7}[[:xdigit:]]{1,4}|([[:xdigit:]]{1,4}-){1,7}-|([[:xdigit:]]{1,4}-){1,6}-[[:xdigit:]]{1,4}|([[:xdigit:]]{1,4}-){1,5}(-[[:xdigit:]]{1,4}){1,2}|([[:xdigit:]]{1,4}-){1,4}(-[[:xdigit:]]{1,4}){1,3}|([[:xdigit:]]{1,4}-){1,3}(-[[:xdigit:]]{1,4}){1,4}|([[:xdigit:]]{1,4}-){1,2}(-[[:xdigit:]]{1,4}){1,5}|[[:xdigit:]]{1,4}-((-[[:xdigit:]]{1,4}){1,6})|-((-[[:xdigit:]]{1,4}){1,7}|-)|fe80-(-[[:xdigit:]]{0,4}){0,4}%[\da-zA-Z]+|--(ffff(-0{1,4})?-)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([[:xdigit:]]{1,4}-){1,4}-((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))($|[.-])`) ipv4ReverseRE = regexp.MustCompile(`^(.*)\.in-addr\.arpa\.$`) ipv6ReverseRE = regexp.MustCompile(`^(([[:xdigit:]]\.){32})ip6\.arpa\.`) dns01ChallengeRE = regexp.MustCompile(`(?i)_acme-challenge\.`) // (?i) → non-capturing case insensitive - mbox, _ = dnsmessage.NewName("briancunnie.gmail.com.") - mx1, _ = dnsmessage.NewName("mail.protonmail.ch.") - mx2, _ = dnsmessage.NewName("mailsec.protonmail.ch.") + mbox, _ = dnsmessage.NewName("briancunnie.gmail.com.") + mx1, _ = dnsmessage.NewName("mail.protonmail.ch.") + mx2, _ = dnsmessage.NewName("mailsec.protonmail.ch.") dkim1Sslip, _ = dnsmessage.NewName("protonmail.domainkey.dw4gykv5i2brtkjglrf34wf6kbxpa5hgtmg2xqopinhgxn5axo73a.domains.proton.ch.") dkim2Sslip, _ = dnsmessage.NewName("protonmail2.domainkey.dw4gykv5i2brtkjglrf34wf6kbxpa5hgtmg2xqopinhgxn5axo73a.domains.proton.ch.") dkim3Sslip, _ = dnsmessage.NewName("protonmail3.domainkey.dw4gykv5i2brtkjglrf34wf6kbxpa5hgtmg2xqopinhgxn5axo73a.domains.proton.ch.") - dkim1Nip, _ = dnsmessage.NewName("protonmail.domainkey.di5fzneyjbxuzcqcrbw2f63m34itvf6lmjde2s4maty3hdt6664dq.domains.proton.ch.") - dkim2Nip, _ = dnsmessage.NewName("protonmail2.domainkey.di5fzneyjbxuzcqcrbw2f63m34itvf6lmjde2s4maty3hdt6664dq.domains.proton.ch.") - dkim3Nip, _ = dnsmessage.NewName("protonmail3.domainkey.di5fzneyjbxuzcqcrbw2f63m34itvf6lmjde2s4maty3hdt6664dq.domains.proton.ch.") + dkim1Nip, _ = dnsmessage.NewName("protonmail.domainkey.di5fzneyjbxuzcqcrbw2f63m34itvf6lmjde2s4maty3hdt6664dq.domains.proton.ch.") + dkim2Nip, _ = dnsmessage.NewName("protonmail2.domainkey.di5fzneyjbxuzcqcrbw2f63m34itvf6lmjde2s4maty3hdt6664dq.domains.proton.ch.") + dkim3Nip, _ = dnsmessage.NewName("protonmail3.domainkey.di5fzneyjbxuzcqcrbw2f63m34itvf6lmjde2s4maty3hdt6664dq.domains.proton.ch.") VersionSemantic = "0.0.0" VersionDate = "0001/01/01-99:99:99-0800" @@ -783,6 +785,28 @@ func NameToA(fqdnString string, allowPublicIPs bool) []dnsmessage.AResource { } } } + if match := ipv4REHex.FindSubmatch(fqdn); match != nil { + hexes := match[2] // strip out leading & trailing "." by using only the 2nd capture group, e.g. "7f000001" + ipBytes := make([]byte, 4) + _, err := hex.Decode(ipBytes, []byte(hexes)) + if err != nil || len(ipBytes) != 4 { + return []dnsmessage.AResource{} + } + ipv4address := net.IPv4(ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3]) + if ipv4address == nil { + return []dnsmessage.AResource{} + } + ipv4address = ipv4address.To4() + if ipv4address == nil { + return []dnsmessage.AResource{} + } + if (!allowPublicIPs) && IsPublic(ipv4address) { + return []dnsmessage.AResource{} + } + return []dnsmessage.AResource{ + {A: [4]byte{ipv4address[0], ipv4address[1], ipv4address[2], ipv4address[3]}}, + } + } return []dnsmessage.AResource{} } diff --git a/xip/xip_test.go b/xip/xip_test.go index f087cea..d6193cb 100644 --- a/xip/xip_test.go +++ b/xip/xip_test.go @@ -198,8 +198,8 @@ var _ = Describe("Xip", func() { DescribeTable("when it succeeds", func(fqdn string, expectedA dnsmessage.AResource) { ipv4Answers := xip.NameToA(fqdn, true) - Expect(len(ipv4Answers)).To(Equal(1)) Expect(ipv4Answers[0]).To(Equal(expectedA)) + Expect(len(ipv4Answers)).To(Equal(1)) }, Entry("custom record", "CusTom.RecOrd.", dnsmessage.AResource{A: [4]byte{78, 46, 204, 247}}), // dots @@ -213,6 +213,18 @@ var _ = Describe("Xip", func() { Entry("IETF protocol assignments with domain and www", "www-192-0-0-1-com", dnsmessage.AResource{A: [4]byte{192, 0, 0, 1}}), // dots-and-dashes, mix-and-matches Entry("Pandaxin's paradox", "minio-01.192-168-1-100.sslip.io", dnsmessage.AResource{A: [4]byte{192, 168, 1, 100}}), + Entry("Hexadecimal #0", "filer.7f000001.sslip.io", dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}}), + Entry("Hexadecimal #1, TLD", "0A09091E", dnsmessage.AResource{A: [4]byte{10, 9, 9, 30}}), + Entry("Hexadecimal #1, TLD #2", "0A09091E.", dnsmessage.AResource{A: [4]byte{10, 9, 9, 30}}), + Entry("Hexadecimal #1, TLD #3", ".0A09091E.", dnsmessage.AResource{A: [4]byte{10, 9, 9, 30}}), + Entry("Hexadecimal #1, TLD #4", "www.0A09091E.", dnsmessage.AResource{A: [4]byte{10, 9, 9, 30}}), + Entry("Hexadecimal #2, mixed case", "ffffFFFF.nip.io", dnsmessage.AResource{A: [4]byte{255, 255, 255, 255}}), + Entry("Hexadecimal #3, different numbers", "www.fedcba98.nip.io", dnsmessage.AResource{A: [4]byte{254, 220, 186, 152}}), + Entry("Hexadecimal #3, different numbers #2", "www.76543210.nip.io", dnsmessage.AResource{A: [4]byte{118, 84, 50, 16}}), + Entry("Hexadecimal #4, dashes trump hex", "www.127-0-0-53.76543210.nip.io", dnsmessage.AResource{A: [4]byte{127, 0, 0, 53}}), + Entry("Hexadecimal #4, dashes trump hex #2", "www.76543210.127-0-0-53.nip.io", dnsmessage.AResource{A: [4]byte{127, 0, 0, 53}}), + Entry("Hexadecimal #4, dots trump hex", "www.127.0.0.53.76543210.nip.io", dnsmessage.AResource{A: [4]byte{127, 0, 0, 53}}), + Entry("Hexadecimal #4, dots trump hex #2", "www.76543210.127.0.0.53.nip.io", dnsmessage.AResource{A: [4]byte{127, 0, 0, 53}}), ) DescribeTable("when it does NOT match an IP address", func(fqdn string) { @@ -228,6 +240,10 @@ var _ = Describe("Xip", func() { Entry("NS but no dot", "ns-hetzner.sslip.io"), Entry("NS + cruft at beginning", "p-ns-hetzner.sslip.io"), Entry("test-net address with dots-and-dashes mixed", "www-192.0-2.3.example-me.com"), + Entry("Hexadecimal with too many digits (9 instead of 8)", "www.0A09091E0.com"), + Entry("Hexadecimal with too few digits (7 instead of 8)", "www.0A09091.com"), + Entry("Hexadecimal with a dash instead of a .", "www-0A09091E.com"), + Entry("Hexadecimal with a dash instead of a . #2", "www.0A09091E-com"), ) When("There is more than one A record", func() { It("returns them all", func() {