]> Cypherpunks repositories - gostls13.git/commitdiff
host and port name lookup
authorRuss Cox <rsc@golang.org>
Thu, 18 Dec 2008 23:42:39 +0000 (15:42 -0800)
committerRuss Cox <rsc@golang.org>
Thu, 18 Dec 2008 23:42:39 +0000 (15:42 -0800)
R=r,presotto
DELTA=1239  (935 added, 281 deleted, 23 changed)
OCL=21041
CL=21539

15 files changed:
src/lib/net/Makefile
src/lib/net/dialgoogle_test.go [new file with mode: 0644]
src/lib/net/dnsclient.go [new file with mode: 0644]
src/lib/net/dnsconfig.go [new file with mode: 0644]
src/lib/net/dnsmsg.go
src/lib/net/ip.go
src/lib/net/ip_test.go [new file with mode: 0644]
src/lib/net/net.go
src/lib/net/parse.go [new file with mode: 0644]
src/lib/net/parse_test.go [new file with mode: 0644]
src/lib/net/port.go [new file with mode: 0644]
src/lib/net/port_test.go [new file with mode: 0644]
src/lib/net/tcpserver_test.go [moved from test/tcpserver.go with 50% similarity]
src/run.bash
test/dialgoogle.go [deleted file]

index 4401f1089b614cc002213800d46e72414a58a9b8..ff5176a1f9c07a48db361d0aff8c46847af47d52 100644 (file)
@@ -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 (file)
index 0000000..c23d7b7
--- /dev/null
@@ -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 (file)
index 0000000..a447d3e
--- /dev/null
@@ -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 (file)
index 0000000..fee20b1
--- /dev/null
@@ -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
+}
+
index 6d23d649c573443436ab2b98464d1dd85737f1d3..fc7dc443789370b6701d043846d51bb3c7d4339c 100644 (file)
@@ -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.
index 47134ca1fb2865bac574cdcebe14fc107f34fb5d..f573c3463b0af54089ac108a364be01ca33709ca 100644 (file)
 
 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 (file)
index 0000000..9f8198e
--- /dev/null
@@ -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);
+               }
+       }
+}
index 79d648847b1b38ef49350437c931f08e00e5b95a..6ea54931f172dca6ad51e87dbe5ecf7321d60cfe 100644 (file)
@@ -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 (file)
index 0000000..d0a8549
--- /dev/null
@@ -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 (file)
index 0000000..7c477eb
--- /dev/null
@@ -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 (file)
index 0000000..5ff1e58
--- /dev/null
@@ -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 (file)
index 0000000..1d7b4c2
--- /dev/null
@@ -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);
+               }
+       }
+}
similarity index 50%
rename from test/tcpserver.go
rename to src/lib/net/tcpserver_test.go
index d8f9e5a7dc45c70e3f9c94465fc62a20c8bc7bb5..2df012bcd2706d4d2f105201ecda636cc1ec0cf1 100644 (file)
@@ -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");
 }
 
index 979a5ac0201145f4bff3096512ae8b853cf3db0a..30166f71e969c31a71f81be5ad3f092a8be95633 100755 (executable)
@@ -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 (file)
index 126ec82..0000000
+++ /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)
-       }
-}