Source file src/net/lookup_windows_test.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package net
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"os/exec"
    14  	"reflect"
    15  	"regexp"
    16  	"sort"
    17  	"strings"
    18  	"syscall"
    19  	"testing"
    20  )
    21  
    22  var nslookupTestServers = []string{"mail.golang.com", "gmail.com"}
    23  var lookupTestIPs = []string{"8.8.8.8", "1.1.1.1"}
    24  
    25  func toJson(v any) string {
    26  	data, _ := json.Marshal(v)
    27  	return string(data)
    28  }
    29  
    30  func testLookup(t *testing.T, fn func(*testing.T, *Resolver, string)) {
    31  	for _, def := range []bool{true, false} {
    32  		def := def
    33  		for _, server := range nslookupTestServers {
    34  			server := server
    35  			var name string
    36  			if def {
    37  				name = "default/"
    38  			} else {
    39  				name = "go/"
    40  			}
    41  			t.Run(name+server, func(t *testing.T) {
    42  				t.Parallel()
    43  				r := DefaultResolver
    44  				if !def {
    45  					r = &Resolver{PreferGo: true}
    46  				}
    47  				fn(t, r, server)
    48  			})
    49  		}
    50  	}
    51  }
    52  
    53  func TestNSLookupMX(t *testing.T) {
    54  	testenv.MustHaveExternalNetwork(t)
    55  
    56  	testLookup(t, func(t *testing.T, r *Resolver, server string) {
    57  		mx, err := r.LookupMX(context.Background(), server)
    58  		if err != nil {
    59  			t.Fatal(err)
    60  		}
    61  		if len(mx) == 0 {
    62  			t.Fatal("no results")
    63  		}
    64  		expected, err := nslookupMX(server)
    65  		if err != nil {
    66  			t.Skipf("skipping failed nslookup %s test: %s", server, err)
    67  		}
    68  		sort.Sort(byPrefAndHost(expected))
    69  		sort.Sort(byPrefAndHost(mx))
    70  		if !reflect.DeepEqual(expected, mx) {
    71  			t.Errorf("different results %s:\texp:%v\tgot:%v", server, toJson(expected), toJson(mx))
    72  		}
    73  	})
    74  }
    75  
    76  func TestNSLookupCNAME(t *testing.T) {
    77  	testenv.MustHaveExternalNetwork(t)
    78  
    79  	testLookup(t, func(t *testing.T, r *Resolver, server string) {
    80  		cname, err := r.LookupCNAME(context.Background(), server)
    81  		if err != nil {
    82  			t.Fatalf("failed %s: %s", server, err)
    83  		}
    84  		if cname == "" {
    85  			t.Fatalf("no result %s", server)
    86  		}
    87  		expected, err := nslookupCNAME(server)
    88  		if err != nil {
    89  			t.Skipf("skipping failed nslookup %s test: %s", server, err)
    90  		}
    91  		if expected != cname {
    92  			t.Errorf("different results %s:\texp:%v\tgot:%v", server, expected, cname)
    93  		}
    94  	})
    95  }
    96  
    97  func TestNSLookupNS(t *testing.T) {
    98  	testenv.MustHaveExternalNetwork(t)
    99  
   100  	testLookup(t, func(t *testing.T, r *Resolver, server string) {
   101  		ns, err := r.LookupNS(context.Background(), server)
   102  		if err != nil {
   103  			t.Fatalf("failed %s: %s", server, err)
   104  		}
   105  		if len(ns) == 0 {
   106  			t.Fatal("no results")
   107  		}
   108  		expected, err := nslookupNS(server)
   109  		if err != nil {
   110  			t.Skipf("skipping failed nslookup %s test: %s", server, err)
   111  		}
   112  		sort.Sort(byHost(expected))
   113  		sort.Sort(byHost(ns))
   114  		if !reflect.DeepEqual(expected, ns) {
   115  			t.Errorf("different results %s:\texp:%v\tgot:%v", toJson(server), toJson(expected), ns)
   116  		}
   117  	})
   118  }
   119  
   120  func TestNSLookupTXT(t *testing.T) {
   121  	testenv.MustHaveExternalNetwork(t)
   122  
   123  	testLookup(t, func(t *testing.T, r *Resolver, server string) {
   124  		txt, err := r.LookupTXT(context.Background(), server)
   125  		if err != nil {
   126  			t.Fatalf("failed %s: %s", server, err)
   127  		}
   128  		if len(txt) == 0 {
   129  			t.Fatalf("no results")
   130  		}
   131  		expected, err := nslookupTXT(server)
   132  		if err != nil {
   133  			t.Skipf("skipping failed nslookup %s test: %s", server, err)
   134  		}
   135  		sort.Strings(expected)
   136  		sort.Strings(txt)
   137  		if !reflect.DeepEqual(expected, txt) {
   138  			t.Errorf("different results %s:\texp:%v\tgot:%v", server, toJson(expected), toJson(txt))
   139  		}
   140  	})
   141  }
   142  
   143  func TestLookupLocalPTR(t *testing.T) {
   144  	testenv.MustHaveExternalNetwork(t)
   145  
   146  	addr, err := localIP()
   147  	if err != nil {
   148  		t.Errorf("failed to get local ip: %s", err)
   149  	}
   150  	names, err := LookupAddr(addr.String())
   151  	if err != nil {
   152  		t.Errorf("failed %s: %s", addr, err)
   153  	}
   154  	if len(names) == 0 {
   155  		t.Errorf("no results")
   156  	}
   157  	expected, err := lookupPTR(addr.String())
   158  	if err != nil {
   159  		t.Skipf("skipping failed lookup %s test: %s", addr.String(), err)
   160  	}
   161  	sort.Strings(expected)
   162  	sort.Strings(names)
   163  	if !reflect.DeepEqual(expected, names) {
   164  		t.Errorf("different results %s:\texp:%v\tgot:%v", addr, toJson(expected), toJson(names))
   165  	}
   166  }
   167  
   168  func TestLookupPTR(t *testing.T) {
   169  	testenv.MustHaveExternalNetwork(t)
   170  
   171  	for _, addr := range lookupTestIPs {
   172  		names, err := LookupAddr(addr)
   173  		if err != nil {
   174  			// The DNSError type stores the error as a string, so it cannot wrap the
   175  			// original error code and we cannot check for it here. However, we can at
   176  			// least use its error string to identify the correct localized text for
   177  			// the error to skip.
   178  			var DNS_ERROR_RCODE_SERVER_FAILURE syscall.Errno = 9002
   179  			if strings.HasSuffix(err.Error(), DNS_ERROR_RCODE_SERVER_FAILURE.Error()) {
   180  				testenv.SkipFlaky(t, 38111)
   181  			}
   182  			t.Errorf("failed %s: %s", addr, err)
   183  		}
   184  		if len(names) == 0 {
   185  			t.Errorf("no results")
   186  		}
   187  		expected, err := lookupPTR(addr)
   188  		if err != nil {
   189  			t.Logf("skipping failed lookup %s test: %s", addr, err)
   190  			continue
   191  		}
   192  		sort.Strings(expected)
   193  		sort.Strings(names)
   194  		if !reflect.DeepEqual(expected, names) {
   195  			t.Errorf("different results %s:\texp:%v\tgot:%v", addr, toJson(expected), toJson(names))
   196  		}
   197  	}
   198  }
   199  
   200  type byPrefAndHost []*MX
   201  
   202  func (s byPrefAndHost) Len() int { return len(s) }
   203  func (s byPrefAndHost) Less(i, j int) bool {
   204  	if s[i].Pref != s[j].Pref {
   205  		return s[i].Pref < s[j].Pref
   206  	}
   207  	return s[i].Host < s[j].Host
   208  }
   209  func (s byPrefAndHost) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   210  
   211  type byHost []*NS
   212  
   213  func (s byHost) Len() int           { return len(s) }
   214  func (s byHost) Less(i, j int) bool { return s[i].Host < s[j].Host }
   215  func (s byHost) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   216  
   217  func nslookup(qtype, name string) (string, error) {
   218  	var out strings.Builder
   219  	var err strings.Builder
   220  	cmd := exec.Command("nslookup", "-querytype="+qtype, name)
   221  	cmd.Stdout = &out
   222  	cmd.Stderr = &err
   223  	if err := cmd.Run(); err != nil {
   224  		return "", err
   225  	}
   226  	r := strings.ReplaceAll(out.String(), "\r\n", "\n")
   227  	// nslookup stderr output contains also debug information such as
   228  	// "Non-authoritative answer" and it doesn't return the correct errcode
   229  	if strings.Contains(err.String(), "can't find") {
   230  		return r, errors.New(err.String())
   231  	}
   232  	return r, nil
   233  }
   234  
   235  func nslookupMX(name string) (mx []*MX, err error) {
   236  	var r string
   237  	if r, err = nslookup("mx", name); err != nil {
   238  		return
   239  	}
   240  	mx = make([]*MX, 0, 10)
   241  	// linux nslookup syntax
   242  	// golang.org      mail exchanger = 2 alt1.aspmx.l.google.com.
   243  	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+mail exchanger\s*=\s*([0-9]+)\s*([a-z0-9.\-]+)$`)
   244  	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
   245  		pref, _, _ := dtoi(ans[2])
   246  		mx = append(mx, &MX{absDomainName(ans[3]), uint16(pref)})
   247  	}
   248  	// windows nslookup syntax
   249  	// gmail.com       MX preference = 30, mail exchanger = alt3.gmail-smtp-in.l.google.com
   250  	rx = regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+MX preference\s*=\s*([0-9]+)\s*,\s*mail exchanger\s*=\s*([a-z0-9.\-]+)$`)
   251  	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
   252  		pref, _, _ := dtoi(ans[2])
   253  		mx = append(mx, &MX{absDomainName(ans[3]), uint16(pref)})
   254  	}
   255  	return
   256  }
   257  
   258  func nslookupNS(name string) (ns []*NS, err error) {
   259  	var r string
   260  	if r, err = nslookup("ns", name); err != nil {
   261  		return
   262  	}
   263  	ns = make([]*NS, 0, 10)
   264  	// golang.org      nameserver = ns1.google.com.
   265  	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+nameserver\s*=\s*([a-z0-9.\-]+)$`)
   266  	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
   267  		ns = append(ns, &NS{absDomainName(ans[2])})
   268  	}
   269  	return
   270  }
   271  
   272  func nslookupCNAME(name string) (cname string, err error) {
   273  	var r string
   274  	if r, err = nslookup("cname", name); err != nil {
   275  		return
   276  	}
   277  	// mail.golang.com canonical name = golang.org.
   278  	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+canonical name\s*=\s*([a-z0-9.\-]+)$`)
   279  	// assumes the last CNAME is the correct one
   280  	last := name
   281  	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
   282  		last = ans[2]
   283  	}
   284  	return absDomainName(last), nil
   285  }
   286  
   287  func nslookupTXT(name string) (txt []string, err error) {
   288  	var r string
   289  	if r, err = nslookup("txt", name); err != nil {
   290  		return
   291  	}
   292  	txt = make([]string, 0, 10)
   293  	// linux
   294  	// golang.org      text = "v=spf1 redirect=_spf.google.com"
   295  
   296  	// windows
   297  	// golang.org      text =
   298  	//
   299  	//    "v=spf1 redirect=_spf.google.com"
   300  	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+text\s*=\s*"(.*)"$`)
   301  	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
   302  		txt = append(txt, ans[2])
   303  	}
   304  	return
   305  }
   306  
   307  func ping(name string) (string, error) {
   308  	cmd := exec.Command("ping", "-n", "1", "-a", name)
   309  	stdoutStderr, err := cmd.CombinedOutput()
   310  	if err != nil {
   311  		return "", fmt.Errorf("%v: %v", err, string(stdoutStderr))
   312  	}
   313  	r := strings.ReplaceAll(string(stdoutStderr), "\r\n", "\n")
   314  	return r, nil
   315  }
   316  
   317  func lookupPTR(name string) (ptr []string, err error) {
   318  	var r string
   319  	if r, err = ping(name); err != nil {
   320  		return
   321  	}
   322  	ptr = make([]string, 0, 10)
   323  	rx := regexp.MustCompile(`(?m)^Pinging\s+([a-zA-Z0-9.\-]+)\s+\[.*$`)
   324  	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
   325  		ptr = append(ptr, absDomainName(ans[1]))
   326  	}
   327  	return
   328  }
   329  
   330  func localIP() (ip IP, err error) {
   331  	conn, err := Dial("udp", "golang.org:80")
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	defer conn.Close()
   336  
   337  	localAddr := conn.LocalAddr().(*UDPAddr)
   338  
   339  	return localAddr.IP, nil
   340  }
   341  

View as plain text