diff --git a/client/dns.go b/client/dns.go index 2026cbf..61492db 100644 --- a/client/dns.go +++ b/client/dns.go @@ -1,9 +1,12 @@ package client import ( + "context" + "errors" "net" "strconv" "strings" + "time" ) func LookupMinecraftSRV(host string, port uint16) (hostRet string, portRet uint16) { @@ -22,19 +25,76 @@ func LookupMinecraftSRV(host string, port uint16) (hostRet string, portRet uint1 return } -func DoDNS(in string) (out string, err error) { // Bad code should match minecraft +type DNSCheckFunc func(addr string) error + +// DoDns resolves a DNS name to an IP address +// It should resolve a minecraft compatible address into something net.Dial +func DoDns(in string) (out string, err error) { + return DoDNSChecked(in, nil) +} + +// DoDNSChecked resolves a DNS name to an IP address +// It should resolve a minecraft compatible address into something net.Dial +// It also optionally accepts a function to "check" the address +func DoDNSChecked(in string, f DNSCheckFunc) (out string, err error) { // Bad code should match minecraft // https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34 - host, port, _ := net.SplitHostPort(in) - var portN uint16 = 25565 - if port != "" { - var porti int - porti, err = strconv.Atoi(port) - if err != nil { + host, port, err := net.SplitHostPort(in) + if err != nil { + var addrErr *net.AddrError + if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" { + err = nil + host = in + port = "25565" + } else { return } - portN = uint16(porti) } - host, portN = LookupMinecraftSRV(host, portN) - out = net.JoinHostPort(host, strconv.Itoa(int(portN))) + porti, err := strconv.Atoi(port) + if err != nil { + return + } + if f == nil { + f = func(addr string) error { return nil } + } + + err = f(host) + if err != nil { + return + } + portN := uint16(porti) + outIP, err := net.ResolveIPAddr("ip", host) + if err != nil { + return + } + err = f(outIP.String()) + if err != nil { + return + } + out = outIP.String() + ":" + port + if portN != 25565 { + + return + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) // otherwise this takes 10+ seconds + defer cancel() + + r := &net.Resolver{} + + _, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host) + if len(srvs) == 0 { + + return + } + host = strings.TrimSuffix(srvs[0].Target, ".") + portN = srvs[0].Port + outIP, err = net.ResolveIPAddr("ip", host) + if err != nil { + return + } + err = f(outIP.String()) + if err != nil { + return + } + out = outIP.String() + ":" + port return } diff --git a/client/dns_test.go b/client/dns_test.go new file mode 100644 index 0000000..ad57cf8 --- /dev/null +++ b/client/dns_test.go @@ -0,0 +1,21 @@ +package client + +import "testing" + +func TestDoDNS(t *testing.T) { + type Case struct { + Input string + Output string + } + for _, c := range []Case{{"localhost", "127.0.0.1:25565"}, {"1f2d.net", "50.114.4.126:25565"}} { + t.Logf("Testing %s", c.Input) + out, err := DoDns(c.Input) + if err != nil { + t.Error(err) + continue + } + if out != c.Output { + t.Errorf("Expected %s, got %s", c.Output, out) + } + } +}