| // 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 | 
 | } |