host and port name lookup

R=r,presotto
DELTA=1239  (935 added, 281 deleted, 23 changed)
OCL=21041
CL=21539
diff --git a/src/lib/net/Makefile b/src/lib/net/Makefile
index 4401f10..ff5176a 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 @@
 	$(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 0000000..c23d7b7
--- /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 0000000..a447d3e
--- /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 0000000..fee20b1
--- /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 6d23d64..fc7dc44 100644
--- a/src/lib/net/dnsmsg.go
+++ b/src/lib/net/dnsmsg.go
@@ -63,6 +63,14 @@
 	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 47134ca..f573c34 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 @@
 	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 @@
 			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 @@
 	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 0000000..9f8198e
--- /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 79d6488..6ea5493 100644
--- a/src/lib/net/net.go
+++ b/src/lib/net/net.go
@@ -16,10 +16,13 @@
 	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 @@
 		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 @@
 // 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 @@
 		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 @@
 	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 @@
 	}
 
 	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 0000000..d0a8549
--- /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 0000000..7c477eb
--- /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 0000000..5ff1e58
--- /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 0000000..1d7b4c2
--- /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/src/lib/net/tcpserver_test.go b/src/lib/net/tcpserver_test.go
new file mode 100644
index 0000000..2df012b
--- /dev/null
+++ b/src/lib/net/tcpserver_test.go
@@ -0,0 +1,84 @@
+// 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 (
+	"os";
+	"io";
+	"net";
+	"testing";
+)
+
+func Echo(fd io.ReadWrite, done *chan<- int) {
+	var buf [1024]byte;
+
+	for {
+		n, err := fd.Read(&buf);
+		if err != nil || n == 0 {
+			break;
+		}
+		fd.Write((&buf)[0:n])
+	}
+	done <- 1
+}
+
+func Serve(t *testing.T, network, addr string, listening, done *chan<- int) {
+	l, err := net.Listen(network, addr);
+	if err != nil {
+		t.Fatalf("net.Listen(%q, %q) = _, %v", network, addr, err);
+	}
+	listening <- 1;
+
+	for {
+		fd, addr, err := l.Accept();
+		if err != nil {
+			break;
+		}
+		echodone := new(chan int);
+		go Echo(fd, echodone);
+		<-echodone;	// make sure Echo stops
+		l.Close();
+	}
+	done <- 1
+}
+
+func Connect(t *testing.T, network, addr string) {
+	fd, err := net.Dial(network, "", addr);
+	if err != nil {
+		t.Fatalf("net.Dial(%q, %q, %q) = _, %v", network, "", addr, err);
+	}
+
+	b := io.StringBytes("hello, world\n");
+	var b1 [100]byte;
+
+	n, errno := fd.Write(b);
+	if n != len(b) {
+		t.Fatalf("fd.Write(%q) = %d, %v", b, n, errno);
+	}
+
+	n, errno = fd.Read(&b1);
+	if n != len(b) {
+		t.Fatalf("fd.Read() = %d, %v", n, errno);
+	}
+	fd.Close();
+}
+
+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(t, network, listenaddr, listening, done);
+	<-listening;	// wait for server to start
+	Connect(t, network, dialaddr);
+	<-done;	// make sure server stopped
+}
+
+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 979a5ac..30166f7 100755
--- a/src/run.bash
+++ b/src/run.bash
@@ -28,6 +28,7 @@
 	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 126ec82..0000000
--- 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)
-	}
-}
diff --git a/test/tcpserver.go b/test/tcpserver.go
deleted file mode 100644
index d8f9e5a..0000000
--- a/test/tcpserver.go
+++ /dev/null
@@ -1,97 +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 (
-	"os";
-	"io";
-	"net";
-	"syscall"
-)
-
-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;
-
-	for {
-		n, err := fd.Read(&buf);
-		if err != nil || n == 0 {
-			break;
-		}
-		fd.Write((&buf)[0:n])
-	}
-	done <- 1
-}
-
-func Serve(network, addr string, listening, done *chan<- int) {
-	l, err := net.Listen(network, addr);
-	if err != nil {
-		panic("listen: "+err.String());
-	}
-	listening <- 1;
-
-	for {
-		fd, addr, err := l.Accept();
-		if err != nil {
-			break;
-		}
-		echodone := new(chan int);
-		go Echo(fd, echodone);
-		<-echodone;	// make sure Echo stops
-		l.Close();
-	}
-	done <- 1
-}
-
-func Connect(network, addr string) {
-	fd, err := net.Dial(network, "", addr);
-	if err != nil {
-		panic("connect: "+err.String());
-	}
-
-	b := StringToBuf("hello, world\n");
-	var b1 [100]byte;
-
-	n, errno := fd.Write(b);
-	if n != len(b) {
-		panic("syscall.write in connect");
-	}
-
-	n, errno = fd.Read(&b1);
-	if n != len(b) {
-		panic("syscall.read in connect");
-	}
-
-//	os.Stdout.Write((&b1)[0:n]);
-	fd.Close();
-}
-
-func Test(network, listenaddr, dialaddr string) {
-//	print("Test ", network, " ", listenaddr, " ", dialaddr, "\n");
-	listening := new(chan int);
-	done := new(chan int);
-	go Serve(network, listenaddr, listening, done);
-	<-listening;	// wait for server to start
-	Connect(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
-}
-