318 lines
8 KiB
Go
318 lines
8 KiB
Go
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package net
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"internal/testenv"
|
||
|
"os/exec"
|
||
|
"reflect"
|
||
|
"regexp"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
)
|
||
|
|
||
|
var nslookupTestServers = []string{"mail.golang.com", "gmail.com"}
|
||
|
var lookupTestIPs = []string{"8.8.8.8", "1.1.1.1"}
|
||
|
|
||
|
func toJson(v any) string {
|
||
|
data, _ := json.Marshal(v)
|
||
|
return string(data)
|
||
|
}
|
||
|
|
||
|
func TestNSLookupMX(t *testing.T) {
|
||
|
testenv.MustHaveExternalNetwork(t)
|
||
|
|
||
|
for _, server := range nslookupTestServers {
|
||
|
mx, err := LookupMX(server)
|
||
|
if err != nil {
|
||
|
t.Error(err)
|
||
|
continue
|
||
|
}
|
||
|
if len(mx) == 0 {
|
||
|
t.Errorf("no results")
|
||
|
continue
|
||
|
}
|
||
|
expected, err := nslookupMX(server)
|
||
|
if err != nil {
|
||
|
t.Logf("skipping failed nslookup %s test: %s", server, err)
|
||
|
}
|
||
|
sort.Sort(byPrefAndHost(expected))
|
||
|
sort.Sort(byPrefAndHost(mx))
|
||
|
if !reflect.DeepEqual(expected, mx) {
|
||
|
t.Errorf("different results %s:\texp:%v\tgot:%v", server, toJson(expected), toJson(mx))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNSLookupCNAME(t *testing.T) {
|
||
|
testenv.MustHaveExternalNetwork(t)
|
||
|
|
||
|
for _, server := range nslookupTestServers {
|
||
|
cname, err := LookupCNAME(server)
|
||
|
if err != nil {
|
||
|
t.Errorf("failed %s: %s", server, err)
|
||
|
continue
|
||
|
}
|
||
|
if cname == "" {
|
||
|
t.Errorf("no result %s", server)
|
||
|
}
|
||
|
expected, err := nslookupCNAME(server)
|
||
|
if err != nil {
|
||
|
t.Logf("skipping failed nslookup %s test: %s", server, err)
|
||
|
continue
|
||
|
}
|
||
|
if expected != cname {
|
||
|
t.Errorf("different results %s:\texp:%v\tgot:%v", server, expected, cname)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNSLookupNS(t *testing.T) {
|
||
|
testenv.MustHaveExternalNetwork(t)
|
||
|
|
||
|
for _, server := range nslookupTestServers {
|
||
|
ns, err := LookupNS(server)
|
||
|
if err != nil {
|
||
|
t.Errorf("failed %s: %s", server, err)
|
||
|
continue
|
||
|
}
|
||
|
if len(ns) == 0 {
|
||
|
t.Errorf("no results")
|
||
|
continue
|
||
|
}
|
||
|
expected, err := nslookupNS(server)
|
||
|
if err != nil {
|
||
|
t.Logf("skipping failed nslookup %s test: %s", server, err)
|
||
|
continue
|
||
|
}
|
||
|
sort.Sort(byHost(expected))
|
||
|
sort.Sort(byHost(ns))
|
||
|
if !reflect.DeepEqual(expected, ns) {
|
||
|
t.Errorf("different results %s:\texp:%v\tgot:%v", toJson(server), toJson(expected), ns)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNSLookupTXT(t *testing.T) {
|
||
|
testenv.MustHaveExternalNetwork(t)
|
||
|
|
||
|
for _, server := range nslookupTestServers {
|
||
|
txt, err := LookupTXT(server)
|
||
|
if err != nil {
|
||
|
t.Errorf("failed %s: %s", server, err)
|
||
|
continue
|
||
|
}
|
||
|
if len(txt) == 0 {
|
||
|
t.Errorf("no results")
|
||
|
continue
|
||
|
}
|
||
|
expected, err := nslookupTXT(server)
|
||
|
if err != nil {
|
||
|
t.Logf("skipping failed nslookup %s test: %s", server, err)
|
||
|
continue
|
||
|
}
|
||
|
sort.Strings(expected)
|
||
|
sort.Strings(txt)
|
||
|
if !reflect.DeepEqual(expected, txt) {
|
||
|
t.Errorf("different results %s:\texp:%v\tgot:%v", server, toJson(expected), toJson(txt))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestLookupLocalPTR(t *testing.T) {
|
||
|
testenv.MustHaveExternalNetwork(t)
|
||
|
|
||
|
addr, err := localIP()
|
||
|
if err != nil {
|
||
|
t.Errorf("failed to get local ip: %s", err)
|
||
|
}
|
||
|
names, err := LookupAddr(addr.String())
|
||
|
if err != nil {
|
||
|
t.Errorf("failed %s: %s", addr, err)
|
||
|
}
|
||
|
if len(names) == 0 {
|
||
|
t.Errorf("no results")
|
||
|
}
|
||
|
expected, err := lookupPTR(addr.String())
|
||
|
if err != nil {
|
||
|
t.Logf("skipping failed lookup %s test: %s", addr.String(), err)
|
||
|
}
|
||
|
sort.Strings(expected)
|
||
|
sort.Strings(names)
|
||
|
if !reflect.DeepEqual(expected, names) {
|
||
|
t.Errorf("different results %s:\texp:%v\tgot:%v", addr, toJson(expected), toJson(names))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestLookupPTR(t *testing.T) {
|
||
|
testenv.MustHaveExternalNetwork(t)
|
||
|
|
||
|
for _, addr := range lookupTestIPs {
|
||
|
names, err := LookupAddr(addr)
|
||
|
if err != nil {
|
||
|
t.Errorf("failed %s: %s", addr, err)
|
||
|
}
|
||
|
if len(names) == 0 {
|
||
|
t.Errorf("no results")
|
||
|
}
|
||
|
expected, err := lookupPTR(addr)
|
||
|
if err != nil {
|
||
|
t.Logf("skipping failed lookup %s test: %s", addr, err)
|
||
|
}
|
||
|
sort.Strings(expected)
|
||
|
sort.Strings(names)
|
||
|
if !reflect.DeepEqual(expected, names) {
|
||
|
t.Errorf("different results %s:\texp:%v\tgot:%v", addr, toJson(expected), toJson(names))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type byPrefAndHost []*MX
|
||
|
|
||
|
func (s byPrefAndHost) Len() int { return len(s) }
|
||
|
func (s byPrefAndHost) Less(i, j int) bool {
|
||
|
if s[i].Pref != s[j].Pref {
|
||
|
return s[i].Pref < s[j].Pref
|
||
|
}
|
||
|
return s[i].Host < s[j].Host
|
||
|
}
|
||
|
func (s byPrefAndHost) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||
|
|
||
|
type byHost []*NS
|
||
|
|
||
|
func (s byHost) Len() int { return len(s) }
|
||
|
func (s byHost) Less(i, j int) bool { return s[i].Host < s[j].Host }
|
||
|
func (s byHost) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||
|
|
||
|
func nslookup(qtype, name string) (string, error) {
|
||
|
var out bytes.Buffer
|
||
|
var err bytes.Buffer
|
||
|
cmd := exec.Command("nslookup", "-querytype="+qtype, name)
|
||
|
cmd.Stdout = &out
|
||
|
cmd.Stderr = &err
|
||
|
if err := cmd.Run(); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
r := strings.ReplaceAll(out.String(), "\r\n", "\n")
|
||
|
// nslookup stderr output contains also debug information such as
|
||
|
// "Non-authoritative answer" and it doesn't return the correct errcode
|
||
|
if strings.Contains(err.String(), "can't find") {
|
||
|
return r, errors.New(err.String())
|
||
|
}
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
func nslookupMX(name string) (mx []*MX, err error) {
|
||
|
var r string
|
||
|
if r, err = nslookup("mx", name); err != nil {
|
||
|
return
|
||
|
}
|
||
|
mx = make([]*MX, 0, 10)
|
||
|
// linux nslookup syntax
|
||
|
// golang.org mail exchanger = 2 alt1.aspmx.l.google.com.
|
||
|
rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+mail exchanger\s*=\s*([0-9]+)\s*([a-z0-9.\-]+)$`)
|
||
|
for _, ans := range rx.FindAllStringSubmatch(r, -1) {
|
||
|
pref, _, _ := dtoi(ans[2])
|
||
|
mx = append(mx, &MX{absDomainName(ans[3]), uint16(pref)})
|
||
|
}
|
||
|
// windows nslookup syntax
|
||
|
// gmail.com MX preference = 30, mail exchanger = alt3.gmail-smtp-in.l.google.com
|
||
|
rx = regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+MX preference\s*=\s*([0-9]+)\s*,\s*mail exchanger\s*=\s*([a-z0-9.\-]+)$`)
|
||
|
for _, ans := range rx.FindAllStringSubmatch(r, -1) {
|
||
|
pref, _, _ := dtoi(ans[2])
|
||
|
mx = append(mx, &MX{absDomainName(ans[3]), uint16(pref)})
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func nslookupNS(name string) (ns []*NS, err error) {
|
||
|
var r string
|
||
|
if r, err = nslookup("ns", name); err != nil {
|
||
|
return
|
||
|
}
|
||
|
ns = make([]*NS, 0, 10)
|
||
|
// golang.org nameserver = ns1.google.com.
|
||
|
rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+nameserver\s*=\s*([a-z0-9.\-]+)$`)
|
||
|
for _, ans := range rx.FindAllStringSubmatch(r, -1) {
|
||
|
ns = append(ns, &NS{absDomainName(ans[2])})
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func nslookupCNAME(name string) (cname string, err error) {
|
||
|
var r string
|
||
|
if r, err = nslookup("cname", name); err != nil {
|
||
|
return
|
||
|
}
|
||
|
// mail.golang.com canonical name = golang.org.
|
||
|
rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+canonical name\s*=\s*([a-z0-9.\-]+)$`)
|
||
|
// assumes the last CNAME is the correct one
|
||
|
last := name
|
||
|
for _, ans := range rx.FindAllStringSubmatch(r, -1) {
|
||
|
last = ans[2]
|
||
|
}
|
||
|
return absDomainName(last), nil
|
||
|
}
|
||
|
|
||
|
func nslookupTXT(name string) (txt []string, err error) {
|
||
|
var r string
|
||
|
if r, err = nslookup("txt", name); err != nil {
|
||
|
return
|
||
|
}
|
||
|
txt = make([]string, 0, 10)
|
||
|
// linux
|
||
|
// golang.org text = "v=spf1 redirect=_spf.google.com"
|
||
|
|
||
|
// windows
|
||
|
// golang.org text =
|
||
|
//
|
||
|
// "v=spf1 redirect=_spf.google.com"
|
||
|
rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+text\s*=\s*"(.*)"$`)
|
||
|
for _, ans := range rx.FindAllStringSubmatch(r, -1) {
|
||
|
txt = append(txt, ans[2])
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func ping(name string) (string, error) {
|
||
|
cmd := exec.Command("ping", "-n", "1", "-a", name)
|
||
|
stdoutStderr, err := cmd.CombinedOutput()
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("%v: %v", err, string(stdoutStderr))
|
||
|
}
|
||
|
r := strings.ReplaceAll(string(stdoutStderr), "\r\n", "\n")
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
func lookupPTR(name string) (ptr []string, err error) {
|
||
|
var r string
|
||
|
if r, err = ping(name); err != nil {
|
||
|
return
|
||
|
}
|
||
|
ptr = make([]string, 0, 10)
|
||
|
rx := regexp.MustCompile(`(?m)^Pinging\s+([a-zA-Z0-9.\-]+)\s+\[.*$`)
|
||
|
for _, ans := range rx.FindAllStringSubmatch(r, -1) {
|
||
|
ptr = append(ptr, absDomainName(ans[1]))
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func localIP() (ip IP, err error) {
|
||
|
conn, err := Dial("udp", "golang.org:80")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
|
||
|
localAddr := conn.LocalAddr().(*UDPAddr)
|
||
|
|
||
|
return localAddr.IP, nil
|
||
|
}
|