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

View as plain text