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
-}
-