github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/net/lookup_windows_test.go (about) 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 }