mirror of
https://github.com/cunnie/sslip.io.git
synced 2025-10-07 00:23:44 +08:00
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.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user