From 83348f956ec9eeb1a9202d135c680f36d1f39a9c Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 18 Dec 2008 15:42:39 -0800 Subject: [PATCH] host and port name lookup R=r,presotto DELTA=1239 (935 added, 281 deleted, 23 changed) OCL=21041 CL=21539 --- src/lib/net/Makefile | 35 ++- src/lib/net/dialgoogle_test.go | 89 ++++++++ src/lib/net/dnsclient.go | 215 ++++++++++++++++++ src/lib/net/dnsconfig.go | 109 +++++++++ src/lib/net/dnsmsg.go | 8 + src/lib/net/ip.go | 60 +---- src/lib/net/ip_test.go | 53 +++++ src/lib/net/net.go | 71 +++--- src/lib/net/parse.go | 156 +++++++++++++ src/lib/net/parse_test.go | 46 ++++ src/lib/net/port.go | 68 ++++++ src/lib/net/port_test.go | 59 +++++ .../lib/net/tcpserver_test.go | 49 ++-- src/run.bash | 1 + test/dialgoogle.go | 111 --------- 15 files changed, 886 insertions(+), 244 deletions(-) create mode 100644 src/lib/net/dialgoogle_test.go create mode 100644 src/lib/net/dnsclient.go create mode 100644 src/lib/net/dnsconfig.go create mode 100644 src/lib/net/ip_test.go create mode 100644 src/lib/net/parse.go create mode 100644 src/lib/net/parse_test.go create mode 100644 src/lib/net/port.go create mode 100644 src/lib/net/port_test.go rename test/tcpserver.go => src/lib/net/tcpserver_test.go (50%) delete mode 100644 test/dialgoogle.go diff --git a/src/lib/net/Makefile b/src/lib/net/Makefile index 4401f1089b..ff5176a1f9 100644 --- a/src/lib/net/Makefile +++ b/src/lib/net/Makefile @@ -3,7 +3,8 @@ # license that can be found in the LICENSE file. # DO NOT EDIT. Automatically generated by gobuild. -# gobuild -m fd_darwin.go fd.go net.go net_darwin.go ip.go dnsmsg.go >Makefile +# gobuild -m dnsclient.go dnsconfig.go dnsmsg.go fd.go fd_darwin.go\ +# ip.go net.go net_darwin.go parse.go port.go >Makefile O=6 GC=$(O)g CC=$(O)c -w @@ -32,37 +33,55 @@ coverage: packages $(AS) $*.s O1=\ - fd_$(GOOS).$O\ - ip.$O\ dnsmsg.$O\ + fd_$(GOOS).$O\ + parse.$O\ O2=\ fd.$O\ - net_$(GOOS).$O\ + ip.$O\ + port.$O\ O3=\ + dnsconfig.$O\ + net_$(GOOS).$O\ + +O4=\ net.$O\ -net.a: a1 a2 a3 +O5=\ + dnsclient.$O\ + +net.a: a1 a2 a3 a4 a5 a1: $(O1) - $(AR) grc net.a fd_$(GOOS).$O ip.$O dnsmsg.$O + $(AR) grc net.a dnsmsg.$O fd_$(GOOS).$O parse.$O rm -f $(O1) a2: $(O2) - $(AR) grc net.a fd.$O net_$(GOOS).$O + $(AR) grc net.a fd.$O ip.$O port.$O rm -f $(O2) a3: $(O3) - $(AR) grc net.a net.$O + $(AR) grc net.a dnsconfig.$O net_$(GOOS).$O rm -f $(O3) +a4: $(O4) + $(AR) grc net.a net.$O + rm -f $(O4) + +a5: $(O5) + $(AR) grc net.a dnsclient.$O + rm -f $(O5) + newpkg: clean $(AR) grc net.a $(O1): newpkg $(O2): a1 $(O3): a2 +$(O4): a3 +$(O5): a4 nuke: clean rm -f $(GOROOT)/pkg/net.a diff --git a/src/lib/net/dialgoogle_test.go b/src/lib/net/dialgoogle_test.go new file mode 100644 index 0000000000..c23d7b7c7b --- /dev/null +++ b/src/lib/net/dialgoogle_test.go @@ -0,0 +1,89 @@ +// 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 ( + "net"; + "flag"; + "io"; + "os"; + "testing"; +) + +// If an IPv6 tunnel is running (see go/stubl), we can try dialing a real IPv6 address. +var ipv6 = false +var ipv6_flag = flag.Bool("ipv6", false, &ipv6, "assume ipv6 tunnel is present") + +// fd is already connected to www.google.com port 80. +// Run an HTTP request to fetch the main page. +func FetchGoogle(t *testing.T, fd net.Conn, network, addr string) { + req := io.StringBytes("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); + n, errno := fd.Write(req); + + buf := new([1000]byte); + n, errno = io.Readn(fd, buf); + + if n < 1000 { + t.Errorf("FetchGoogle: short HTTP read from %s %s", network, addr); + return + } +} + +func DoDial(t *testing.T, network, addr string) { + fd, err := net.Dial(network, "", addr); + if err != nil { + t.Errorf("net.Dial(%q, %q, %q) = _, %v", network, "", addr, err); + return + } + FetchGoogle(t, fd, network, addr); + fd.Close() +} + +func DoDialTCP(t *testing.T, network, addr string) { + fd, err := net.DialTCP(network, "", addr); + if err != nil { + t.Errorf("net.DialTCP(%q, %q, %q) = _, %v", network, "", addr, err); + } else { + FetchGoogle(t, fd, network, addr); + } + fd.Close() +} + +var googleaddrs = []string { + "74.125.19.99:80", + "www.google.com:80", + "74.125.19.99:http", + "www.google.com:http", + "074.125.019.099:0080", + "[::ffff:74.125.19.99]:80", + "[::ffff:4a7d:1363]:80", + "[0:0:0:0:0000:ffff:74.125.19.99]:80", + "[0:0:0:0:000000:ffff:74.125.19.99]:80", + "[0:0:0:0:0:ffff::74.125.19.99]:80", + "[2001:4860:0:2001::68]:80" // ipv6.google.com; removed if ipv6 flag not set +} + +export func TestDialGoogle(t *testing.T) { + // If no ipv6 tunnel, don't try the last address. + if !ipv6 { + googleaddrs[len(googleaddrs)-1] = "" + } + + for i := 0; i < len(googleaddrs); i++ { + addr := googleaddrs[i]; + if addr == "" { + continue + } + t.Logf("-- %s --", addr); + DoDial(t, "tcp", addr); + DoDialTCP(t, "tcp", addr); + if addr[0] != '[' { + DoDial(t, "tcp4", addr); + DoDialTCP(t, "tcp4", addr) + } + DoDial(t, "tcp6", addr); + DoDialTCP(t, "tcp6", addr) + } +} diff --git a/src/lib/net/dnsclient.go b/src/lib/net/dnsclient.go new file mode 100644 index 0000000000..a447d3e91b --- /dev/null +++ b/src/lib/net/dnsclient.go @@ -0,0 +1,215 @@ +// 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. + +// DNS client. +// Has to be linked into package net for Dial. + +// TODO(rsc): +// Check periodically whether /etc/resolv.conf has changed. +// Could potentially handle many outstanding lookups faster. +// Could have a small cache. +// Random UDP source port (net.Dial should do that for us). +// Random request IDs. +// More substantial error reporting. +// Remove use of fmt? + +package net + +import ( + "fmt"; + "io"; + "net"; + "once"; + "os"; + "strings"; +) + +export var ( + DNS_InternalError = os.NewError("internal dns error"); + DNS_MissingConfig = os.NewError("no dns configuration"); + DNS_NoAnswer = os.NewError("dns got no answer"); + DNS_BadRequest = os.NewError("malformed dns request"); + DNS_BadReply = os.NewError("malformed dns reply"); + DNS_ServerFailure = os.NewError("dns server failure"); + DNS_NoServers = os.NewError("no dns servers"); + DNS_NameTooLong = os.NewError("dns name too long"); + DNS_RedirectLoop = os.NewError("dns redirect loop"); + DNS_NameNotFound = os.NewError("dns name not found"); +); + +// Send a request on the connection and hope for a reply. +// Up to cfg.attempts attempts. +func Exchange(cfg *DNS_Config, c Conn, name string) (m *DNS_Msg, err *os.Error) { + if len(name) >= 256 { + return nil, DNS_NameTooLong + } + out := new(DNS_Msg); + out.id = 0x1234; + out.question = &[]DNS_Question{ + DNS_Question{ name, DNS_TypeA, DNS_ClassINET } + }; + out.recursion_desired = true; + msg, ok := out.Pack(); + if !ok { + return nil, DNS_InternalError + } + + for attempt := 0; attempt < cfg.attempts; attempt++ { + n, err := c.Write(msg); + if err != nil { + return nil, err + } + + // TODO(rsc): set up timeout or call ReadTimeout. + // right now net does not support that. + + buf := new([]byte, 2000); // More than enough. + n, err = c.Read(buf); + if err != nil { + // TODO(rsc): only continue if timed out + continue + } + buf = buf[0:n]; + in := new(DNS_Msg); + if !in.Unpack(buf) || in.id != out.id { + continue + } + return in, nil + } + return nil, DNS_NoAnswer +} + +// Find answer for name in dns message. +// On return, if err == nil, addrs != nil. +// TODO(rsc): Maybe return *[]*[]byte (==*[]IPAddr) instead? +func Answer(name string, dns *DNS_Msg) (addrs *[]string, err *os.Error) { + addrs = new([]string, len(dns.answer))[0:0]; + + if dns.rcode == DNS_RcodeNameError && dns.authoritative { + return nil, DNS_NameNotFound // authoritative "no such host" + } + if dns.rcode != DNS_RcodeSuccess { + // None of the error codes make sense + // for the query we sent. If we didn't get + // a name error and we didn't get success, + // the server is behaving incorrectly. + return nil, DNS_ServerFailure + } + + // Look for the name. + // Presotto says it's okay to assume that servers listed in + // /etc/resolv.conf are recursive resolvers. + // We asked for recursion, so it should have included + // all the answers we need in this one packet. +Cname: + for cnameloop := 0; cnameloop < 10; cnameloop++ { + addrs = addrs[0:0]; + for i := 0; i < len(dns.answer); i++ { + rr := dns.answer[i]; + h := rr.Header(); + if h.class == DNS_ClassINET && h.name == name { + switch h.rrtype { + case DNS_TypeA: + n := len(addrs); + a := rr.(*DNS_RR_A).a; + addrs = addrs[0:n+1]; + addrs[n] = fmt.sprintf("%d.%d.%d.%d", (a>>24), (a>>16)&0xFF, (a>>8)&0xFF, a&0xFF); + case DNS_TypeCNAME: + // redirect to cname + name = rr.(*DNS_RR_CNAME).cname; + continue Cname + } + } + } + if len(addrs) == 0 { + return nil, DNS_NameNotFound + } + return addrs, nil + } + + // Too many redirects + return nil, DNS_RedirectLoop +} + +// Do a lookup for a single name, which must be rooted +// (otherwise Answer will not find the answers). +func TryOneName(cfg *DNS_Config, name string) (addrs *[]string, err *os.Error) { + err = DNS_NoServers; + for i := 0; i < len(cfg.servers); i++ { + // Calling Dial here is scary -- we have to be sure + // not to dial a name that will require a DNS lookup, + // or Dial will call back here to translate it. + // The DNS config parser has already checked that + // all the cfg.servers[i] are IP addresses, which + // Dial will use without a DNS lookup. + c, cerr := Dial("udp", "", cfg.servers[i] + ":53"); + if cerr != nil { + err = cerr; + continue; + } + msg, merr := Exchange(cfg, c, name); + c.Close(); + if merr != nil { + err = merr; + continue; + } + addrs, aerr := Answer(name, msg); + if aerr != nil && aerr != DNS_NameNotFound { + err = aerr; + continue; + } + return addrs, aerr; + } + return; +} + +var cfg *DNS_Config + +func LoadConfig() { + cfg = DNS_ReadConfig(); +} + +export func LookupHost(name string) (name1 string, addrs *[]string, err *os.Error) { + // TODO(rsc): Pick out obvious non-DNS names to avoid + // sending stupid requests to the server? + + once.Do(&LoadConfig); + if cfg == nil { + err = DNS_MissingConfig; + return; + } + + // If name is rooted (trailing dot) or has enough dots, + // try it by itself first. + rooted := len(name) > 0 && name[len(name)-1] == '.'; + if rooted || strings.count(name, ".") >= cfg.ndots { + rname := name; + if !rooted { + rname += "."; + } + // Can try as ordinary name. + addrs, aerr := TryOneName(cfg, rname); + if aerr == nil { + return rname, addrs, nil; + } + err = aerr; + } + if rooted { + return + } + + // Otherwise, try suffixes. + for i := 0; i < len(cfg.search); i++ { + newname := name+"."+cfg.search[i]; + if newname[len(newname)-1] != '.' { + newname += "." + } + addrs, aerr := TryOneName(cfg, newname); + if aerr == nil { + return newname, addrs, nil; + } + err = aerr; + } + return +} diff --git a/src/lib/net/dnsconfig.go b/src/lib/net/dnsconfig.go new file mode 100644 index 0000000000..fee20b1cc3 --- /dev/null +++ b/src/lib/net/dnsconfig.go @@ -0,0 +1,109 @@ +// 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. + +// Read system DNS config from /etc/resolv.conf + +package net + +import ( + "io"; + "net"; + "os"; + "strconv"; +) + +export type DNS_Config struct { + servers *[]string; // servers to use + search *[]string; // suffixes to append to local name + ndots int; // number of dots in name to trigger absolute lookup + timeout int; // seconds before giving up on packet + attempts int; // lost packets before giving up on server + rotate bool; // round robin among servers +} + +// See resolv.conf(5) on a Linux machine. +// TODO(rsc): Supposed to call uname() and chop the beginning +// of the host name to get the default search domain. +// We assume it's in resolv.conf anyway. +export func DNS_ReadConfig() *DNS_Config { + file := Open("/etc/resolv.conf"); + if file == nil { + return nil + } + conf := new(DNS_Config); + conf.servers = new([]string, 3)[0:0]; // small, but the standard limit + conf.search = new([]string, 0); + conf.ndots = 1; + conf.timeout = 1; + conf.attempts = 1; + conf.rotate = false; + var err *os.Error; + for line, ok := file.ReadLine(); ok; line, ok = file.ReadLine() { + f := GetFields(line); + if len(f) < 1 { + continue; + } + switch f[0] { + case "nameserver": // add one name server + a := conf.servers; + n := len(a); + if len(f) > 1 && n < cap(a) { + // One more check: make sure server name is + // just an IP address. Otherwise we need DNS + // to look it up. + name := f[1]; + if ParseIP(name) != nil { + a = a[0:n+1]; + a[n] = name; + conf.servers = a; + } + } + + case "domain": // set search path to just this domain + if len(f) > 1 { + conf.search = new([]string, 1); + conf.search[0] = f[1]; + } else { + conf.search = new([]string, 0) + } + + case "search": // set search path to given servers + conf.search = new([]string, len(f) - 1); + for i := 0; i < len(conf.search); i++ { + conf.search[i] = f[i+1]; + } + + case "options": // magic options + for i := 1; i < len(f); i++ { + s := f[i]; + switch { + case len(s) >= 6 && s[0:6] == "ndots:": + n, i, ok := Dtoi(s, 6); + if n < 1 { + n = 1 + } + conf.ndots = n; + case len(s) >= 8 && s[0:8] == "timeout:": + n, i, ok := Dtoi(s, 8); + if n < 1 { + n = 1 + } + conf.timeout = n; + case len(s) >= 8 && s[0:9] == "attempts:": + n, i, ok := Dtoi(s, 9); + if n < 1 { + n = 1 + } + conf.attempts = n; + case s == "rotate": + conf.rotate = true; + } + } + } + } + file.Close(); + + return conf +} + diff --git a/src/lib/net/dnsmsg.go b/src/lib/net/dnsmsg.go index 6d23d649c5..fc7dc44378 100644 --- a/src/lib/net/dnsmsg.go +++ b/src/lib/net/dnsmsg.go @@ -63,6 +63,14 @@ export const ( DNS_ClassCHAOS = 3; DNS_ClassHESIOD = 4; DNS_ClassANY = 255; + + // DNS_Msg.rcode + DNS_RcodeSuccess = 0; + DNS_RcodeFormatError = 1; + DNS_RcodeServerFailure = 2; + DNS_RcodeNameError = 3; + DNS_RcodeNotImplemented = 4; + DNS_RcodeRefused = 5; ) // The wire format for the DNS packet header. diff --git a/src/lib/net/ip.go b/src/lib/net/ip.go index 47134ca1fb..f573c3463b 100644 --- a/src/lib/net/ip.go +++ b/src/lib/net/ip.go @@ -12,6 +12,10 @@ package net +import ( + "net" +) + export const ( IPv4len = 4; IPv6len = 16 @@ -240,58 +244,6 @@ export func MaskToString(mask *[]byte) string { return IPToString(mask) } -// Parsing. - -// Bigger than we need, not too big to worry about overflow -const Big = 0xFFFFFF - -// Decimal to integer starting at &s[i]. -// Returns number, new offset, success. -func dtoi(s string, i int) (n int, i1 int, ok bool) { - if len(s) <= i || s[i] < '0' || s[i] > '9' { - return 0, i, false - } - n = 0; - for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { - n = n*10 + int(s[i] - '0'); - if n >= Big { - return 0, i, false - } - } - return n, i, true -} - -// Is b a hex digit? -func ishex(b byte) bool { - return '0' <= b && b <= '9' - || 'a' <= b && b <= 'f' - || 'A' <= b && b <= 'F' -} - -// Hexadecimal to integer starting at &s[i]. -// Returns number, new offset, success. -func xtoi(s string, i int) (n int, i1 int, ok bool) { - if len(s) <= i || !ishex(s[i]) { - return 0, i, false - } - - n = 0; - for ; i < len(s) && ishex(s[i]); i++ { - n *= 16; - if '0' <= s[i] && s[i] <= '9' { - n += int(s[i] - '0') - } else if 'a' <= s[i] && s[i] <= 'f' { - n += int(s[i] - 'a') + 10 - } else { - n += int(s[i] -'A') + 10 - } - if n >= Big { - return 0, i, false - } - } - return n, i, true -} - // Parse IPv4 address (d.d.d.d). func ParseIPv4(s string) *[]byte { var p [IPv4len]byte; @@ -307,7 +259,7 @@ func ParseIPv4(s string) *[]byte { n int; ok bool ) - n, i, ok = dtoi(s, i); + n, i, ok = Dtoi(s, i); if !ok || n > 0xFF { return nil } @@ -346,7 +298,7 @@ func ParseIPv6(s string) *[]byte { j := 0; L: for j < IPv6len { // Hex number. - n, i1, ok := xtoi(s, i); + n, i1, ok := Xtoi(s, i); if !ok || n > 0xFFFF { return nil } diff --git a/src/lib/net/ip_test.go b/src/lib/net/ip_test.go new file mode 100644 index 0000000000..9f8198ee75 --- /dev/null +++ b/src/lib/net/ip_test.go @@ -0,0 +1,53 @@ +// 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 ( + "net"; + "testing" +) + +func IPv4(a, b, c, d byte) *[]byte { + return &[]byte{ 0,0,0,0, 0,0,0,0, 0,0,255,255, a,b,c,d } +} + +func Equal(a *[]byte, b *[]byte) bool { + if a == b { + return true + } + if a == nil || b == nil || len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +type ParseIPTest struct { + in string; + out *[]byte; +} +var parseiptests = []ParseIPTest { + ParseIPTest{"127.0.1.2", IPv4(127, 0, 1, 2)}, + ParseIPTest{"127.0.0.1", IPv4(127, 0, 0, 1)}, + ParseIPTest{"127.0.0.256", nil}, + ParseIPTest{"abc", nil}, + ParseIPTest{"::ffff:127.0.0.1", IPv4(127, 0, 0, 1)}, + ParseIPTest{"2001:4860:0:2001::68", + &[]byte{0x20,0x01, 0x48,0x60, 0,0, 0x20,0x01, 0,0, 0,0, 0,0, 0x00,0x68}}, + ParseIPTest{"::ffff:4a7d:1363", IPv4(74, 125, 19, 99)}, +} + +export func TestParseIP(t *testing.T) { + for i := 0; i < len(parseiptests); i++ { + tt := parseiptests[i]; + if out := ParseIP(tt.in); !Equal(out, tt.out) { + t.Errorf("ParseIP(%#q) = %v, want %v", tt.in, out, tt.out); + } + } +} diff --git a/src/lib/net/net.go b/src/lib/net/net.go index 79d648847b..6ea54931f1 100644 --- a/src/lib/net/net.go +++ b/src/lib/net/net.go @@ -16,10 +16,13 @@ export var ( MissingAddress = os.NewError("missing address"); UnknownNetwork = os.NewError("unknown network"); UnknownHost = os.NewError("unknown host"); + DNS_Error = os.NewError("dns error looking up host"); UnknownPort = os.NewError("unknown port"); UnknownSocketFamily = os.NewError("unknown socket family"); ) +export func LookupHost(name string) (name1 string, addrs *[]string, err *os.Error) + // Split "host:port" into "host" and "port". // Host cannot contain colons unless it is bracketed. func SplitHostPort(hostport string) (host, port string, err *os.Error) { @@ -42,10 +45,8 @@ func SplitHostPort(hostport string) (host, port string, err *os.Error) { host = host[1:len(host)-1] } else { // ... but if there are no brackets, no colons. - for i := 0; i < len(host); i++ { - if host[i] == ':' { - return "", "", BadAddress - } + if ByteIndex(host, ':') >= 0 { + return "", "", BadAddress } } return host, port, nil @@ -55,28 +56,12 @@ func SplitHostPort(hostport string) (host, port string, err *os.Error) { // If host contains colons, will join into "[host]:port". func JoinHostPort(host, port string) string { // If host has colons, have to bracket it. - for i := 0; i < len(host); i++ { - if host[i] == ':' { - return "[" + host + "]:" + port - } + if ByteIndex(host, ':') >= 0 { + return "[" + host + "]:" + port } return host + ":" + port } -func xdtoi(s string) (n int, ok bool) { - if s == "" || s[0] < '0' || s[0] > '9' { - return 0, false - } - n = 0; - for i := 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { - n = n*10 + int(s[i] - '0'); - if n >= 1000000 { // bigger than we need - return 0, false - } - } - return n, true -} - // Convert "host:port" into IP address and port. // For now, host and port must be numeric literals. // Eventually, we'll have name resolution. @@ -87,18 +72,33 @@ func HostPortToIP(net string, hostport string) (ip *[]byte, iport int, err *os.E return nil, 0, err } - // TODO: Resolve host. - + // Try as an IP address. addr := ParseIP(host); if addr == nil { - return nil, 0, UnknownHost + // Not an IP address. Try as a DNS name. + hostname, addrs, err := LookupHost(host); + if err != nil { + return nil, 0, err + } + if len(addrs) == 0 { + return nil, 0, UnknownHost + } + addr = ParseIP(addrs[0]); + if addr == nil { + // should not happen + return nil, 0, BadAddress + } } - // TODO: Resolve port. - - p, ok := xdtoi(port); - if !ok || p < 0 || p > 0xFFFF { - return nil, 0, UnknownPort + p, i, ok := Dtoi(port, 0); + if !ok || i != len(port) { + p, ok = LookupPort(net, port); + if !ok { + return nil, 0, UnknownPort + } + } + if p < 0 || p > 0xFFFF { + return nil, 0, BadAddress } return addr, p, nil @@ -284,13 +284,7 @@ func InternetSocket(net, laddr, raddr string, proto int64) (fd *FD, err *os.Erro var lip, rip *[]byte; var lport, rport int; var lerr, rerr *os.Error; -// BUG 6g doesn't zero var lists -lip = nil; -rip = nil; -lport = 0; -rport = 0; -lerr = nil; -rerr = nil; + if laddr != "" { lip, lport, lerr = HostPortToIP(net, laddr); if lerr != nil { @@ -335,9 +329,6 @@ rerr = nil; } var la, ra *syscall.Sockaddr; -// BUG -la = nil; -ra = nil; if lip != nil { la, lerr = cvt(lip, lport); if lerr != nil { diff --git a/src/lib/net/parse.go b/src/lib/net/parse.go new file mode 100644 index 0000000000..d0a8549c4f --- /dev/null +++ b/src/lib/net/parse.go @@ -0,0 +1,156 @@ +// 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. + +// Simple file i/o and string manipulation, to avoid +// depending on strconv and bufio. + +package net + +import ( + "io"; + "os"; +) + +package type File struct { + fd *os.FD; + data *[]byte; +} + +func (f *File) Close() { + f.fd.Close() +} + +func (f *File) GetLineFromData() (s string, ok bool) { + data := f.data; + for i := 0; i < len(data); i++ { + if data[i] == '\n' { + s = string(data[0:i]); + ok = true; + // move data + i++; + n := len(data) - i; + for j := 0; j < n; j++ { + data[j] = data[i+j]; + } + f.data = data[0:n]; + return + } + } + return +} + +func (f *File) ReadLine() (s string, ok bool) { + if s, ok = f.GetLineFromData(); ok { + return + } + if len(f.data) < cap(f.data) { + ln := len(f.data); + n, err := io.Readn(f.fd, f.data[ln:cap(f.data)]); + if n >= 0 { + f.data = f.data[0:ln+n]; + } + } + s, ok = f.GetLineFromData(); + return +} + +package func Open(name string) *File { + fd, err := os.Open(name, os.O_RDONLY, 0); + if err != nil { + return nil + } + return &File{fd, new([]byte, 1024)[0:0]}; +} + +package func ByteIndex(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} + +// Count occurrences in s of any bytes in t. +package func CountAnyByte(s string, t string) int { + n := 0; + for i := 0; i < len(s); i++ { + if ByteIndex(t, s[i]) >= 0 { + n++; + } + } + return n +} + +// Split s at any bytes in t. +package func SplitAtBytes(s string, t string) *[]string { + a := new([]string, 1+CountAnyByte(s, t)); + n := 0; + last := 0; + for i := 0; i < len(s); i++ { + if ByteIndex(t, s[i]) >= 0 { + if last < i { + a[n] = string(s[last:i]); + n++; + } + last = i+1; + } + } + if last < len(s) { + a[n] = string(s[last:len(s)]); + n++; + } + return a[0:n]; +} + +package func GetFields(s string) *[]string { + return SplitAtBytes(s, " \r\t\n"); +} + +// Bigger than we need, not too big to worry about overflow +const Big = 0xFFFFFF + +// Decimal to integer starting at &s[i0]. +// Returns number, new offset, success. +package func Dtoi(s string, i0 int) (n int, i int, ok bool) { + n = 0; + for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i] - '0'); + if n >= Big { + return 0, i, false + } + } + if i == i0 { + return 0, i, false + } + return n, i, true +} + +// Hexadecimal to integer starting at &s[i0]. +// Returns number, new offset, success. +package func Xtoi(s string, i0 int) (n int, i int, ok bool) { + n = 0; + for i = i0; i < len(s); i++ { + if '0' <= s[i] && s[i] <= '9' { + n *= 16; + n += int(s[i] - '0') + } else if 'a' <= s[i] && s[i] <= 'f' { + n *= 16; + n += int(s[i] - 'a') + 10 + } else if 'A' <= s[i] && s[i] <= 'F' { + n *= 16; + n += int(s[i] -'A') + 10 + } else { + break + } + if n >= Big { + return 0, i, false + } + } + if i == i0 { + return 0, i, false + } + return n, i, true +} + diff --git a/src/lib/net/parse_test.go b/src/lib/net/parse_test.go new file mode 100644 index 0000000000..7c477eb0b4 --- /dev/null +++ b/src/lib/net/parse_test.go @@ -0,0 +1,46 @@ +// 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 ( + "bufio"; + "net"; + "os"; + "testing"; +) + +export func TestReadLine(t *testing.T) { + filename := "/etc/services"; // a nice big file + + fd, err := os.Open(filename, os.O_RDONLY, 0); + if err != nil { + t.Fatalf("open %s: %v", filename, err); + } + br, err1 := bufio.NewBufRead(fd); + if err1 != nil { + t.Fatalf("bufio.NewBufRead: %v", err1); + } + + file := Open(filename); + if file == nil { + t.Fatalf("net.Open(%s) = nil", filename); + } + + lineno := 1; + byteno := 0; + for { + bline, berr := br.ReadLineString('\n', false); + line, ok := file.ReadLine(); + if (berr != nil) != !ok || bline != line { + t.Fatalf("%s:%d (#%d)\nbufio => %q, %v\nnet => %q, %v", + filename, lineno, byteno, bline, berr, line, ok); + } + if !ok { + break + } + lineno++; + byteno += len(line) + 1; + } +} diff --git a/src/lib/net/port.go b/src/lib/net/port.go new file mode 100644 index 0000000000..5ff1e5805a --- /dev/null +++ b/src/lib/net/port.go @@ -0,0 +1,68 @@ +// 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. + +// Read system port mappings from /etc/services + +package net + +import ( + "io"; + "net"; + "once"; + "os"; + "strconv"; +) + +var services *map[string] *map[string] int + +func ReadServices() { + services = new(map[string] *map[string] int); + file := Open("/etc/services"); + for line, ok := file.ReadLine(); ok; line, ok = file.ReadLine() { + // "http 80/tcp www www-http # World Wide Web HTTP" + if i := ByteIndex(line, '#'); i >= 0 { + line = line[0:i]; + } + f := GetFields(line); + if len(f) < 2 { + continue; + } + portnet := f[1]; // "tcp/80" + port, j, ok := Dtoi(portnet, 0); + if !ok || port <= 0 || j >= len(portnet) || portnet[j] != '/' { + continue + } + netw := portnet[j+1:len(portnet)]; // "tcp" + m, ok1 := services[netw]; + if !ok1 { + m = new(map[string] int); + services[netw] = m; + } + for i := 0; i < len(f); i++ { + if i != 1 { // f[1] was port/net + m[f[i]] = port; + } + } + } + file.Close(); +} + +export func LookupPort(netw, name string) (port int, ok bool) { + once.Do(&ReadServices); + + switch netw { + case "tcp4", "tcp6": + netw = "tcp"; + case "udp4", "udp6": + netw = "udp"; + } + + m, mok := services[netw]; + if !mok { + return + } + port, ok = m[name]; + return +} + diff --git a/src/lib/net/port_test.go b/src/lib/net/port_test.go new file mode 100644 index 0000000000..1d7b4c2c30 --- /dev/null +++ b/src/lib/net/port_test.go @@ -0,0 +1,59 @@ +// 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 ( + "net"; + "testing"; +) + +type PortTest struct { + netw string; + name string; + port int; + ok bool; +} + +var porttests = []PortTest { + PortTest{ "tcp", "echo", 7, true }, + PortTest{ "tcp", "discard", 9, true }, + PortTest{ "tcp", "systat", 11, true }, + PortTest{ "tcp", "daytime", 13, true }, + PortTest{ "tcp", "chargen", 19, true }, + PortTest{ "tcp", "ftp-data", 20, true }, + PortTest{ "tcp", "ftp", 21, true }, + PortTest{ "tcp", "ssh", 22, true }, + PortTest{ "tcp", "telnet", 23, true }, + PortTest{ "tcp", "smtp", 25, true }, + PortTest{ "tcp", "time", 37, true }, + PortTest{ "tcp", "domain", 53, true }, + PortTest{ "tcp", "gopher", 70, true }, + PortTest{ "tcp", "finger", 79, true }, + PortTest{ "tcp", "http", 80, true }, + + PortTest{ "udp", "echo", 7, true }, + PortTest{ "udp", "tacacs", 49, true }, + PortTest{ "udp", "tftp", 69, true }, + PortTest{ "udp", "bootpc", 68, true }, + PortTest{ "udp", "bootps", 67, true }, + PortTest{ "udp", "domain", 53, true }, + PortTest{ "udp", "ntp", 123, true }, + PortTest{ "udp", "snmp", 161, true }, + PortTest{ "udp", "syslog", 514, true }, + PortTest{ "udp", "nfs", 2049, true }, + + PortTest{ "--badnet--", "zzz", 0, false }, + PortTest{ "tcp", "--badport--", 0, false }, +} + +export func TestLookupPort(t *testing.T) { + for i := 0; i < len(porttests); i++ { + tt := porttests[i]; + if port, ok := LookupPort(tt.netw, tt.name); port != tt.port || ok != tt.ok { + t.Errorf("LookupPort(%q, %q) = %v, %v; want %v, %v", + tt.netw, tt.name, port, ok, tt.port, tt.ok); + } + } +} diff --git a/test/tcpserver.go b/src/lib/net/tcpserver_test.go similarity index 50% rename from test/tcpserver.go rename to src/lib/net/tcpserver_test.go index d8f9e5a7dc..2df012bcd2 100644 --- a/test/tcpserver.go +++ b/src/lib/net/tcpserver_test.go @@ -2,25 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// $G $F.go && $L $F.$A && ./$A.out +package net -package main import ( "os"; "io"; "net"; - "syscall" + "testing"; ) -func StringToBuf(s string) *[]byte { - l := len(s); - b := new([]byte, l); - for i := 0; i < l; i++ { - b[i] = s[i]; - } - return b; -} - func Echo(fd io.ReadWrite, done *chan<- int) { var buf [1024]byte; @@ -34,10 +24,10 @@ func Echo(fd io.ReadWrite, done *chan<- int) { done <- 1 } -func Serve(network, addr string, listening, done *chan<- int) { +func Serve(t *testing.T, network, addr string, listening, done *chan<- int) { l, err := net.Listen(network, addr); if err != nil { - panic("listen: "+err.String()); + t.Fatalf("net.Listen(%q, %q) = _, %v", network, addr, err); } listening <- 1; @@ -54,44 +44,41 @@ func Serve(network, addr string, listening, done *chan<- int) { done <- 1 } -func Connect(network, addr string) { +func Connect(t *testing.T, network, addr string) { fd, err := net.Dial(network, "", addr); if err != nil { - panic("connect: "+err.String()); + t.Fatalf("net.Dial(%q, %q, %q) = _, %v", network, "", addr, err); } - b := StringToBuf("hello, world\n"); + b := io.StringBytes("hello, world\n"); var b1 [100]byte; n, errno := fd.Write(b); if n != len(b) { - panic("syscall.write in connect"); + t.Fatalf("fd.Write(%q) = %d, %v", b, n, errno); } n, errno = fd.Read(&b1); if n != len(b) { - panic("syscall.read in connect"); + t.Fatalf("fd.Read() = %d, %v", n, errno); } - -// os.Stdout.Write((&b1)[0:n]); fd.Close(); } -func Test(network, listenaddr, dialaddr string) { -// print("Test ", network, " ", listenaddr, " ", dialaddr, "\n"); +func DoTest(t *testing.T, network, listenaddr, dialaddr string) { + t.Logf("Test %s %s %s\n", network, listenaddr, dialaddr); listening := new(chan int); done := new(chan int); - go Serve(network, listenaddr, listening, done); + go Serve(t, network, listenaddr, listening, done); <-listening; // wait for server to start - Connect(network, dialaddr); + Connect(t, network, dialaddr); <-done; // make sure server stopped } -func main() { - Test("tcp", "0.0.0.0:9999", "127.0.0.1:9999"); - Test("tcp", "[::]:9999", "[::ffff:127.0.0.1]:9999"); - Test("tcp", "[::]:9999", "127.0.0.1:9999"); - Test("tcp", "0.0.0.0:9999", "[::ffff:127.0.0.1]:9999"); - sys.exit(0); // supposed to happen on return, doesn't +export func TestTcpServer(t *testing.T) { + DoTest(t, "tcp", "0.0.0.0:9999", "127.0.0.1:9999"); + DoTest(t, "tcp", "[::]:9999", "[::ffff:127.0.0.1]:9999"); + DoTest(t, "tcp", "[::]:9999", "127.0.0.1:9999"); + DoTest(t, "tcp", "0.0.0.0:9999", "[::ffff:127.0.0.1]:9999"); } diff --git a/src/run.bash b/src/run.bash index 979a5ac020..30166f71e9 100755 --- a/src/run.bash +++ b/src/run.bash @@ -28,6 +28,7 @@ maketest \ lib/hash\ lib/json\ lib/math\ + lib/net\ lib/reflect\ lib/regexp\ lib/strconv\ diff --git a/test/dialgoogle.go b/test/dialgoogle.go deleted file mode 100644 index 126ec824ff..0000000000 --- a/test/dialgoogle.go +++ /dev/null @@ -1,111 +0,0 @@ -// 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. - -// $G $F.go && $L $F.$A && ./$A.out - -package main - -import ( - "net"; - "flag"; - "io"; - "os"; - "syscall" -) - -// If an IPv6 tunnel is running (see go/stubl), we can try dialing a real IPv6 address. -var ipv6 = false -var ipv6_flag = flag.Bool("ipv6", false, &ipv6, "assume ipv6 tunnel is present") - -func StringToBuf(s string) *[]byte -{ - l := len(s); - b := new([]byte, l); - for i := 0; i < l; i++ { - b[i] = s[i]; - } - return b; -} - -func Readn(fd io.Read, buf *[]byte) (n int, err *os.Error) { - n = 0; - for n < len(buf) { - nn, e := fd.Read(buf[n:len(buf)]); - if nn > 0 { - n += nn - } - if e != nil { - return n, e - } - } - return n, nil -} - - -// fd is already connected to www.google.com port 80. -// Run an HTTP request to fetch the main page. -func FetchGoogle(fd net.Conn) { - req := StringToBuf("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); - n, errno := fd.Write(req); - - buf := new([1000]byte); - n, errno = Readn(fd, buf); - - fd.Close(); - if n < 1000 { - panic("short http read"); - } -} - -func TestDial(network, addr string) { - fd, err := net.Dial(network, "", addr); - if err != nil { - panic("net.Dial ", network, " ", addr, ": ", err.String()) - } - FetchGoogle(fd) -} - -func TestDialTCP(network, addr string) { - fd, err := net.DialTCP(network, "", addr); - if err != nil { - panic("net.DialTCP ", network, " ", addr, ": ", err.String()) - } - FetchGoogle(fd) -} - -var addrs = []string { - "74.125.19.99:80", - "074.125.019.099:0080", - "[::ffff:74.125.19.99]:80", - "[::ffff:4a7d:1363]:80", - "[0:0:0:0:0000:ffff:74.125.19.99]:80", - "[0:0:0:0:000000:ffff:74.125.19.99]:80", - "[0:0:0:0:0:ffff::74.125.19.99]:80", - "[2001:4860:0:2001::68]:80" // ipv6.google.com; removed if ipv6 flag not set -} - -func main() -{ - flag.Parse(); - // If no ipv6 tunnel, don't try the last address. - if !ipv6 { - addrs[len(addrs)-1] = "" - } - - for i := 0; i < len(addrs); i++ { - addr := addrs[i]; - if addr == "" { - continue - } - // print(addr, "\n"); - TestDial("tcp", addr); - TestDialTCP("tcp", addr); - if addr[0] != '[' { - TestDial("tcp4", addr); - TestDialTCP("tcp4", addr) - } - TestDial("tcp6", addr); - TestDialTCP("tcp6", addr) - } -} -- 2.48.1