From 16a757694e9139f5994733410fa3d711031a1580 Mon Sep 17 00:00:00 2001 From: m5r Date: Fri, 28 Oct 2022 23:36:57 +0200 Subject: [PATCH] reorganize code --- README.md | 4 ++ main.go | 103 +++++------------------------------------- xip/xip.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 93 deletions(-) create mode 100644 xip/xip.go diff --git a/README.md b/README.md index 1e4bcaa..773bd40 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ It was inspired by [local-ip.co](http://local-ip.co) and ```sh $ dig 10-0-1-29.my.local-ip.dev +short 10.0.1.29 +$ dig app.10-0-1-29.my.local-ip.dev +short +10.0.1.29 +$ dig foo.bar.10.0.1.29.my.local-ip.dev +short +10.0.1.29 $ dig 127.0.0.1.my.local-ip.dev +short 127.0.0.1 ``` diff --git a/main.go b/main.go index 07800cc..f595256 100644 --- a/main.go +++ b/main.go @@ -1,104 +1,21 @@ package main import ( - "fmt" - "log" - "net" - "os" - "regexp" - "strconv" + "flag" "strings" - "github.com/miekg/dns" + xip "local-ip.dev/xip" ) -const zone = "my.local-ip.dev." +const ( + zone = "my.local-ip.dev." + nameservers = "ns.local-ip.dev." +) func main() { - dns.HandleFunc(zone, handleDnsRequest) + var port = flag.Int("port", 53, "port the DNS server should bind to") + flag.Parse() - port, err := strconv.Atoi(os.Getenv("PORT")) - if err != nil { - port = 9053 - } - server := &dns.Server{ - Addr: ":" + strconv.Itoa(port), - Net: "udp", - } - log.Printf("Starting at %d\n", port) - err = server.ListenAndServe() - defer server.Shutdown() - if err != nil { - log.Fatalf("Failed to start server: %s\n ", err.Error()) - } -} - -func handleDnsRequest(response dns.ResponseWriter, request *dns.Msg) { - go func() { - message := new(dns.Msg) - message.SetReply(request) - message.Compress = true - - switch request.Opcode { - case dns.OpcodeQuery: - handleQuery(message) - default: - refuseMessage(message) - } - - response.WriteMsg(message) - }() -} - -func handleQuery(message *dns.Msg) { - for _, question := range message.Question { - switch question.Qtype { - case dns.TypeA: - ip := extractIp(question.Name) - log.Printf("%s => %s\n", question.Name, ip) - if ip == nil { - refuseMessage(message) - break - } - - resource := new(dns.A) - resource.Hdr = dns.RR_Header{Ttl: 10, Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET} - resource.A = ip - message.Answer = append(message.Answer, resource) - } - } -} - -func extractIp(fqdn string) net.IP { - var ip string - dashedIpRegex := regexp.MustCompile(fmt.Sprintf(`^(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\-?\b){4}).%s$`, zone)) - matches := dashedIpRegex.FindStringSubmatch(fqdn) - if len(matches) < 2 { - return nil - } - - ip = strings.ReplaceAll(matches[1], "-", ".") - - return net.ParseIP(ip) -} - -func refuseMessage(message *dns.Msg) { - message.MsgHdr.Rcode = dns.RcodeRefused - - soa := new(dns.SOA) - soa.Hdr = dns.RR_Header{ - Name: "my.local-ip.dev.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 10, - Rdlength: 0, - } - soa.Ns = "ns-1.local-ip.dev." - soa.Mbox = "admin.local-ip.dev." - soa.Serial = 2022102400 - soa.Refresh = 10 - soa.Retry = 10 - soa.Expire = 10 - soa.Minttl = 10 - message.Ns = append(message.Ns, soa) + n := xip.NewXip(zone, strings.Split(nameservers, ","), *port) + n.StartServer() } diff --git a/xip/xip.go b/xip/xip.go new file mode 100644 index 0000000..7a18633 --- /dev/null +++ b/xip/xip.go @@ -0,0 +1,130 @@ +package xip + +import ( + "log" + "net" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/miekg/dns" +) + +type Xip struct { + Server dns.Server + NameServers []*dns.NS + Zone string +} + +var ( + flyRegion = os.Getenv("FLY_REGION") + dottedIpV4Regex = regexp.MustCompile(`(?:^|(?:[\w\d])+\.)(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4})($|[.-])`) + dashedIpV4Regex = regexp.MustCompile(`(?:^|(?:[\w\d])+\.)(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\-?\b){4})($|[.-])`) +) + +func (xip *Xip) handleA(question dns.Question, response *dns.Msg) *dns.A { + fqdn := question.Name + + for _, ipV4RE := range []*regexp.Regexp{dashedIpV4Regex, dottedIpV4Regex} { + if ipV4RE.MatchString(fqdn) { + match := ipV4RE.FindStringSubmatch(fqdn)[1] + match = strings.ReplaceAll(match, "-", ".") + ipV4Address := net.ParseIP(match).To4() + if ipV4Address == nil { + return nil + } + + resource := &dns.A{ + Hdr: dns.RR_Header{ + Ttl: uint32((time.Hour * 24 * 7).Seconds()), + Name: fqdn, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: ipV4Address, + } + log.Printf("(%s) %s => %s\n", flyRegion, fqdn, ipV4Address) + return resource + } + } + + return nil +} + +func (xip *Xip) handleQuery(message *dns.Msg) { + for _, question := range message.Question { + switch question.Qtype { + case dns.TypeA: + record := xip.handleA(question, message) + message.Answer = append(message.Answer, record) + } + } +} + +func (xip *Xip) refuseMessage(message *dns.Msg) { + message.MsgHdr.Rcode = dns.RcodeRefused + + soa := new(dns.SOA) + soa.Hdr = dns.RR_Header{ + Name: "my.local-ip.dev.", + Rrtype: dns.TypeSOA, + Class: dns.ClassINET, + Ttl: uint32((time.Hour * 24 * 7).Seconds()), + Rdlength: 0, + } + soa.Ns = "ns.local-ip.dev." + soa.Mbox = "admin.local-ip.dev." + soa.Serial = 2022102800 + soa.Refresh = uint32((time.Minute * 15).Seconds()) + soa.Retry = uint32((time.Minute * 15).Seconds()) + soa.Expire = uint32((time.Minute * 30).Seconds()) + soa.Minttl = uint32((time.Minute * 5).Seconds()) + message.Ns = append(message.Ns, soa) +} + +func (xip *Xip) handleDnsRequest(response dns.ResponseWriter, request *dns.Msg) { + go func() { + message := new(dns.Msg) + message.SetReply(request) + message.Compress = true + message.Authoritative = true + message.RecursionAvailable = false + + switch request.Opcode { + case dns.OpcodeQuery: + xip.handleQuery(message) + default: + xip.refuseMessage(message) + } + + response.WriteMsg(message) + }() +} + +func (xip *Xip) StartServer() { + log.Printf("Listening on %s\n", xip.Server.Addr) + err := xip.Server.ListenAndServe() + defer xip.Server.Shutdown() + if err != nil { + log.Fatalf("Failed to start server: %s\n ", err.Error()) + } +} + +func NewXip(zone string, nameservers []string, port int) (xip *Xip) { + xip = &Xip{} + + for _, ns := range nameservers { + xip.NameServers = append(xip.NameServers, &dns.NS{Ns: ns}) + } + + xip.Server = dns.Server{ + Addr: ":" + strconv.Itoa(port), + Net: "udp", + } + + dns.HandleFunc(zone, xip.handleDnsRequest) + + return xip +}