From 3fc089b7a77a9147d5f377095138e35ab1a669cd Mon Sep 17 00:00:00 2001 From: Brian Cunnie Date: Tue, 9 Feb 2021 06:49:00 -0800 Subject: [PATCH] wildcard-dns-http-server: better error-checking - when DNS gets a permission error, it helpfully suggests using `sudo` - when DNS can't bind to `INADDR_ANY`, it's probably because it's Fedora running `systemd.resolved` on port 53 of 127.0.0.53, so we try to bind to each address individually. - we don't implement similar checks for the HTTP server: - if it's a permission problem, the DNS server has already warned the user. - if it's a binding problem, the user is probably running an HTTP server bound to `INADDR_ANY`, so we might as well exit. - we ported this code from main `sslip.io` DNS server. --- .../src/wildcard-dns-http-server/main.go | 109 ++++++++++++++++-- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/bosh-release/src/wildcard-dns-http-server/main.go b/bosh-release/src/wildcard-dns-http-server/main.go index 41b911c..4e26ae2 100644 --- a/bosh-release/src/wildcard-dns-http-server/main.go +++ b/bosh-release/src/wildcard-dns-http-server/main.go @@ -8,7 +8,11 @@ import ( "log" "net" "net/http" + "os" + "runtime" + "strings" "sync" + "syscall" "golang.org/x/net/dns/dnsmessage" ) @@ -21,24 +25,58 @@ type Txt struct { } func main() { + var wg sync.WaitGroup + log.Println("DNS: starting up.") conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 53}) - if err != nil { - log.Fatal(err.Error()) + switch { + case err == nil: + log.Println(`DNS: Successfully bound to all interfaces, port 53.`) + wg.Add(1) + go dnsServer(conn, &wg) + case isErrorPermissionsError(err): + log.Println("DNS: Try invoking me with `sudo` because I don't have permission to bind to port 53.") + log.Fatal("DNS: " + err.Error()) + case isErrorAddressAlreadyInUse(err): + log.Println(`DNS: I couldn't bind to "0.0.0.0:53" (INADDR_ANY, all interfaces), so I'll try to bind to each address individually.`) + ipCIDRs := listLocalIPCIDRs() + var boundIPsPorts, unboundIPs []string + for _, ipCIDR := range ipCIDRs { + ip, _, err := net.ParseCIDR(ipCIDR) + if err != nil { + log.Printf(`DNS: I couldn't parse the local interface "%s".`, ipCIDR) + continue + } + conn, err = net.ListenUDP("udp", &net.UDPAddr{ + IP: ip, + Port: 53, + Zone: "", + }) + if err != nil { + unboundIPs = append(unboundIPs, ip.String()) + } else { + wg.Add(1) + boundIPsPorts = append(boundIPsPorts, conn.LocalAddr().String()) + go dnsServer(conn, &wg) + } + } + if len(boundIPsPorts) > 0 { + log.Printf(`DNS: I bound to the following: "%s"`, strings.Join(boundIPsPorts, `", "`)) + } + if len(unboundIPs) > 0 { + log.Printf(`DNS: I couldn't bind to the following IPs: "%s"`, strings.Join(unboundIPs, `", "`)) + } + default: + log.Fatal("DNS: " + err.Error()) } - - var group sync.WaitGroup - group.Add(1) - go dnsServer(conn, &group) - group.Add(1) - go httpServer(&group) - group.Wait() + wg.Add(1) + go httpServer(&wg) + wg.Wait() } func dnsServer(conn *net.UDPConn, group *sync.WaitGroup) { var query dnsmessage.Message defer group.Done() - log.Println("DNS: starting up.") queryRaw := make([]byte, 512) for { _, addr, err := conn.ReadFromUDP(queryRaw) @@ -111,6 +149,7 @@ func usageHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) log.Println("HTTP: " + err.Error()) } + log.Printf("HTTP: wrong path (%s) with method (%s).\n", r.URL.Path, r.Method) } func updateTxtHandler(w http.ResponseWriter, r *http.Request) { @@ -143,7 +182,55 @@ func updateTxtHandler(w http.ResponseWriter, r *http.Request) { log.Println("HTTP: " + err.Error()) return } - log.Println("Creating new TXT record \"" + updateTxt.Txt + "\".") + log.Println("HTTP: Creating new TXT record \"" + updateTxt.Txt + "\".") // this is the money shot, where we create a new DNS TXT record to what was in the POST request txts = append(txts, updateTxt.Txt) } +func listLocalIPCIDRs() []string { + var ifaces []net.Interface + var cidrStrings []string + var err error + if ifaces, err = net.Interfaces(); err != nil { + panic(err) + } + for _, iface := range ifaces { + var cidrs []net.Addr + if cidrs, err = iface.Addrs(); err != nil { + panic(err) + } + for _, cidr := range cidrs { + cidrStrings = append(cidrStrings, cidr.String()) + } + } + return cidrStrings +} + +// Thanks https://stackoverflow.com/a/52152912/2510873 +func isErrorAddressAlreadyInUse(err error) bool { + var eOsSyscall *os.SyscallError + if !errors.As(err, &eOsSyscall) { + return false + } + var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr) + if !errors.As(eOsSyscall, &errErrno) { + return false + } + if errErrno == syscall.EADDRINUSE { + return true + } + const WSAEADDRINUSE = 10048 + if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE { + return true + } + return false +} + +func isErrorPermissionsError(err error) bool { + var eOsSyscall *os.SyscallError + if errors.As(err, &eOsSyscall) { + if os.IsPermission(eOsSyscall) { + return true + } + } + return false +}