Home | History | Annotate | Download | only in net
      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 	"bytes"
      9 	"encoding/json"
     10 	"errors"
     11 	"internal/testenv"
     12 	"os/exec"
     13 	"reflect"
     14 	"regexp"
     15 	"sort"
     16 	"strings"
     17 	"testing"
     18 )
     19 
     20 var nslookupTestServers = []string{"mail.golang.com", "gmail.com"}
     21 
     22 func toJson(v interface{}) string {
     23 	data, _ := json.Marshal(v)
     24 	return string(data)
     25 }
     26 
     27 func TestNSLookupMX(t *testing.T) {
     28 	testenv.MustHaveExternalNetwork(t)
     29 
     30 	for _, server := range nslookupTestServers {
     31 		mx, err := LookupMX(server)
     32 		if err != nil {
     33 			t.Error(err)
     34 			continue
     35 		}
     36 		if len(mx) == 0 {
     37 			t.Errorf("no results")
     38 			continue
     39 		}
     40 		expected, err := nslookupMX(server)
     41 		if err != nil {
     42 			t.Logf("skipping failed nslookup %s test: %s", server, err)
     43 		}
     44 		sort.Sort(byPrefAndHost(expected))
     45 		sort.Sort(byPrefAndHost(mx))
     46 		if !reflect.DeepEqual(expected, mx) {
     47 			t.Errorf("different results %s:\texp:%v\tgot:%v", server, toJson(expected), toJson(mx))
     48 		}
     49 	}
     50 }
     51 
     52 func TestNSLookupCNAME(t *testing.T) {
     53 	testenv.MustHaveExternalNetwork(t)
     54 
     55 	for _, server := range nslookupTestServers {
     56 		cname, err := LookupCNAME(server)
     57 		if err != nil {
     58 			t.Errorf("failed %s: %s", server, err)
     59 			continue
     60 		}
     61 		if cname == "" {
     62 			t.Errorf("no result %s", server)
     63 		}
     64 		expected, err := nslookupCNAME(server)
     65 		if err != nil {
     66 			t.Logf("skipping failed nslookup %s test: %s", server, err)
     67 			continue
     68 		}
     69 		if expected != cname {
     70 			t.Errorf("different results %s:\texp:%v\tgot:%v", server, expected, cname)
     71 		}
     72 	}
     73 }
     74 
     75 func TestNSLookupNS(t *testing.T) {
     76 	testenv.MustHaveExternalNetwork(t)
     77 
     78 	for _, server := range nslookupTestServers {
     79 		ns, err := LookupNS(server)
     80 		if err != nil {
     81 			t.Errorf("failed %s: %s", server, err)
     82 			continue
     83 		}
     84 		if len(ns) == 0 {
     85 			t.Errorf("no results")
     86 			continue
     87 		}
     88 		expected, err := nslookupNS(server)
     89 		if err != nil {
     90 			t.Logf("skipping failed nslookup %s test: %s", server, err)
     91 			continue
     92 		}
     93 		sort.Sort(byHost(expected))
     94 		sort.Sort(byHost(ns))
     95 		if !reflect.DeepEqual(expected, ns) {
     96 			t.Errorf("different results %s:\texp:%v\tgot:%v", toJson(server), toJson(expected), ns)
     97 		}
     98 	}
     99 }
    100 
    101 func TestNSLookupTXT(t *testing.T) {
    102 	testenv.MustHaveExternalNetwork(t)
    103 
    104 	for _, server := range nslookupTestServers {
    105 		txt, err := LookupTXT(server)
    106 		if err != nil {
    107 			t.Errorf("failed %s: %s", server, err)
    108 			continue
    109 		}
    110 		if len(txt) == 0 {
    111 			t.Errorf("no results")
    112 			continue
    113 		}
    114 		expected, err := nslookupTXT(server)
    115 		if err != nil {
    116 			t.Logf("skipping failed nslookup %s test: %s", server, err)
    117 			continue
    118 		}
    119 		sort.Strings(expected)
    120 		sort.Strings(txt)
    121 		if !reflect.DeepEqual(expected, txt) {
    122 			t.Errorf("different results %s:\texp:%v\tgot:%v", server, toJson(expected), toJson(txt))
    123 		}
    124 	}
    125 }
    126 
    127 type byPrefAndHost []*MX
    128 
    129 func (s byPrefAndHost) Len() int { return len(s) }
    130 func (s byPrefAndHost) Less(i, j int) bool {
    131 	if s[i].Pref != s[j].Pref {
    132 		return s[i].Pref < s[j].Pref
    133 	}
    134 	return s[i].Host < s[j].Host
    135 }
    136 func (s byPrefAndHost) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
    137 
    138 type byHost []*NS
    139 
    140 func (s byHost) Len() int           { return len(s) }
    141 func (s byHost) Less(i, j int) bool { return s[i].Host < s[j].Host }
    142 func (s byHost) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    143 
    144 func nslookup(qtype, name string) (string, error) {
    145 	var out bytes.Buffer
    146 	var err bytes.Buffer
    147 	cmd := exec.Command("nslookup", "-querytype="+qtype, name)
    148 	cmd.Stdout = &out
    149 	cmd.Stderr = &err
    150 	if err := cmd.Run(); err != nil {
    151 		return "", err
    152 	}
    153 	r := strings.Replace(out.String(), "\r\n", "\n", -1)
    154 	// nslookup stderr output contains also debug information such as
    155 	// "Non-authoritative answer" and it doesn't return the correct errcode
    156 	if strings.Contains(err.String(), "can't find") {
    157 		return r, errors.New(err.String())
    158 	}
    159 	return r, nil
    160 }
    161 
    162 func nslookupMX(name string) (mx []*MX, err error) {
    163 	var r string
    164 	if r, err = nslookup("mx", name); err != nil {
    165 		return
    166 	}
    167 	mx = make([]*MX, 0, 10)
    168 	// linux nslookup syntax
    169 	// golang.org      mail exchanger = 2 alt1.aspmx.l.google.com.
    170 	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+mail exchanger\s*=\s*([0-9]+)\s*([a-z0-9.\-]+)$`)
    171 	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
    172 		pref, _, _ := dtoi(ans[2])
    173 		mx = append(mx, &MX{absDomainName([]byte(ans[3])), uint16(pref)})
    174 	}
    175 	// windows nslookup syntax
    176 	// gmail.com       MX preference = 30, mail exchanger = alt3.gmail-smtp-in.l.google.com
    177 	rx = regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+MX preference\s*=\s*([0-9]+)\s*,\s*mail exchanger\s*=\s*([a-z0-9.\-]+)$`)
    178 	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
    179 		pref, _, _ := dtoi(ans[2])
    180 		mx = append(mx, &MX{absDomainName([]byte(ans[3])), uint16(pref)})
    181 	}
    182 	return
    183 }
    184 
    185 func nslookupNS(name string) (ns []*NS, err error) {
    186 	var r string
    187 	if r, err = nslookup("ns", name); err != nil {
    188 		return
    189 	}
    190 	ns = make([]*NS, 0, 10)
    191 	// golang.org      nameserver = ns1.google.com.
    192 	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+nameserver\s*=\s*([a-z0-9.\-]+)$`)
    193 	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
    194 		ns = append(ns, &NS{absDomainName([]byte(ans[2]))})
    195 	}
    196 	return
    197 }
    198 
    199 func nslookupCNAME(name string) (cname string, err error) {
    200 	var r string
    201 	if r, err = nslookup("cname", name); err != nil {
    202 		return
    203 	}
    204 	// mail.golang.com canonical name = golang.org.
    205 	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+canonical name\s*=\s*([a-z0-9.\-]+)$`)
    206 	// assumes the last CNAME is the correct one
    207 	last := name
    208 	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
    209 		last = ans[2]
    210 	}
    211 	return absDomainName([]byte(last)), nil
    212 }
    213 
    214 func nslookupTXT(name string) (txt []string, err error) {
    215 	var r string
    216 	if r, err = nslookup("txt", name); err != nil {
    217 		return
    218 	}
    219 	txt = make([]string, 0, 10)
    220 	// linux
    221 	// golang.org      text = "v=spf1 redirect=_spf.google.com"
    222 
    223 	// windows
    224 	// golang.org      text =
    225 	//
    226 	//    "v=spf1 redirect=_spf.google.com"
    227 	rx := regexp.MustCompile(`(?m)^([a-z0-9.\-]+)\s+text\s*=\s*"(.*)"$`)
    228 	for _, ans := range rx.FindAllStringSubmatch(r, -1) {
    229 		txt = append(txt, ans[2])
    230 	}
    231 	return
    232 }
    233