diff options
Diffstat (limited to 'src/pkg/net')
-rw-r--r-- | src/pkg/net/Makefile | 96 | ||||
-rw-r--r-- | src/pkg/net/dialgoogle_test.go | 100 | ||||
-rw-r--r-- | src/pkg/net/dnsclient.go | 227 | ||||
-rw-r--r-- | src/pkg/net/dnsconfig.go | 113 | ||||
-rw-r--r-- | src/pkg/net/dnsmsg.go | 679 | ||||
-rw-r--r-- | src/pkg/net/fd.go | 429 | ||||
-rw-r--r-- | src/pkg/net/fd_darwin.go | 115 | ||||
-rw-r--r-- | src/pkg/net/fd_linux.go | 150 | ||||
-rw-r--r-- | src/pkg/net/ip.go | 421 | ||||
-rw-r--r-- | src/pkg/net/ip_test.go | 50 | ||||
-rw-r--r-- | src/pkg/net/net.go | 862 | ||||
-rw-r--r-- | src/pkg/net/parse.go | 160 | ||||
-rw-r--r-- | src/pkg/net/parse_test.go | 44 | ||||
-rw-r--r-- | src/pkg/net/port.go | 77 | ||||
-rw-r--r-- | src/pkg/net/port_test.go | 59 | ||||
-rw-r--r-- | src/pkg/net/server_test.go | 93 | ||||
-rw-r--r-- | src/pkg/net/timeout_test.go | 42 |
17 files changed, 3717 insertions, 0 deletions
diff --git a/src/pkg/net/Makefile b/src/pkg/net/Makefile new file mode 100644 index 000000000..61c872089 --- /dev/null +++ b/src/pkg/net/Makefile @@ -0,0 +1,96 @@ +# 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. + +# DO NOT EDIT. Automatically generated by gobuild. +# gobuild -m dnsclient.go dnsconfig.go dnsmsg.go fd.go fd_${GOOS}.go ip.go net.go parse.go port.go >Makefile + +D= + +include $(GOROOT)/src/Make.$(GOARCH) +AR=gopack + +default: packages + +clean: + rm -rf *.[$(OS)] *.a [$(OS)].out _obj + +test: packages + gotest + +coverage: packages + gotest + 6cov -g `pwd` | grep -v '_test\.go:' + +%.$O: %.go + $(GC) -I_obj $*.go + +%.$O: %.c + $(CC) $*.c + +%.$O: %.s + $(AS) $*.s + +O1=\ + dnsmsg.$O\ + parse.$O\ + +O2=\ + fd_$(GOOS).$O\ + ip.$O\ + port.$O\ + +O3=\ + dnsconfig.$O\ + fd.$O\ + +O4=\ + net.$O\ + +O5=\ + dnsclient.$O\ + + +phases: a1 a2 a3 a4 a5 +_obj$D/net.a: phases + +a1: $(O1) + $(AR) grc _obj$D/net.a dnsmsg.$O parse.$O + rm -f $(O1) + +a2: $(O2) + $(AR) grc _obj$D/net.a fd_$(GOOS).$O ip.$O port.$O + rm -f $(O2) + +a3: $(O3) + $(AR) grc _obj$D/net.a dnsconfig.$O fd.$O + rm -f $(O3) + +a4: $(O4) + $(AR) grc _obj$D/net.a net.$O + rm -f $(O4) + +a5: $(O5) + $(AR) grc _obj$D/net.a dnsclient.$O + rm -f $(O5) + + +newpkg: clean + mkdir -p _obj$D + $(AR) grc _obj$D/net.a + +$(O1): newpkg +$(O2): a1 +$(O3): a2 +$(O4): a3 +$(O5): a4 +$(O6): a5 + +nuke: clean + rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/net.a + +packages: _obj$D/net.a + +install: packages + test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D + cp _obj$D/net.a $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/net.a diff --git a/src/pkg/net/dialgoogle_test.go b/src/pkg/net/dialgoogle_test.go new file mode 100644 index 000000000..1e0c0aaf0 --- /dev/null +++ b/src/pkg/net/dialgoogle_test.go @@ -0,0 +1,100 @@ +// 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 ( + "flag"; + "fmt"; + "io"; + "net"; + "os"; + "syscall"; + "testing"; +) + +// If an IPv6 tunnel is running (see go/stubl), we can try dialing a real IPv6 address. +var ipv6 = flag.Bool("ipv6", false, "assume ipv6 tunnel is present") + +// fd is already connected to the destination, port 80. +// Run an HTTP request to fetch the appropriate page. +func fetchGoogle(t *testing.T, fd net.Conn, network, addr string) { + req := io.StringBytes("GET /intl/en/privacy.html HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); + n, err := fd.Write(req); + + buf := make([]byte, 1000); + n, err = io.FullRead(fd, buf); + + if n < 1000 { + t.Errorf("fetchGoogle: short HTTP read from %s %s - %v", network, addr, err); + 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 +} + +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); + + if !preferIPv4 { + // make sure preferIPv4 flag works. + preferIPv4 = true; + syscall.SocketDisableIPv6 = true; + doDial(t, "tcp4", addr); + doDialTCP(t, "tcp4", addr); + syscall.SocketDisableIPv6 = false; + preferIPv4 = false; + } + } + doDial(t, "tcp6", addr); + doDialTCP(t, "tcp6", addr) + } +} diff --git a/src/pkg/net/dnsclient.go b/src/pkg/net/dnsclient.go new file mode 100644 index 000000000..cfd67eabe --- /dev/null +++ b/src/pkg/net/dnsclient.go @@ -0,0 +1,227 @@ +// 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"; +) + +// DNS errors returned by LookupHost. +type DNSError struct { + os.ErrorString +} +var ( + DNS_InternalError os.Error = &DNSError{"internal dns error"}; + DNS_MissingConfig os.Error = &DNSError{"no dns configuration"}; + DNS_No_Answer os.Error = &DNSError{"dns got no answer"}; + DNS_BadRequest os.Error = &DNSError{"malformed dns request"}; + DNS_BadReply os.Error = &DNSError{"malformed dns reply"}; + DNS_ServerFailure os.Error = &DNSError{"dns server failure"}; + DNS_NoServers os.Error = &DNSError{"no dns servers"}; + DNS_NameTooLong os.Error = &DNSError{"dns name too long"}; + DNS_RedirectLoop os.Error = &DNSError{"dns redirect loop"}; + DNS_NameNotFound os.Error = &DNSError{"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 + } + + c.SetReadTimeout(1e9); // nanoseconds + + buf := make([]byte, 2000); // More than enough. + n, err = c.Read(buf); + if err == os.EAGAIN { + continue; + } + if err != nil { + return nil, err; + } + buf = buf[0:n]; + in := new(_DNS_Msg); + if !in.Unpack(buf) || in.id != out.id { + continue + } + return in, nil + } + return nil, DNS_No_Answer +} + + +// Find answer for name in dns message. +// On return, if err == nil, addrs != nil. +// TODO(rsc): Maybe return []IP instead? +func answer(name string, dns *_DNS_Msg) (addrs []string, err os.Error) { + addrs = make([]string, 0, len(dns.answer)); + + 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 +var dnserr os.Error + +func loadConfig() { + cfg, dnserr = _DNS_ReadConfig(); +} + +// LookupHost looks up the host name using the local DNS resolver. +// It returns the canonical name for the host and an array of that +// host's addresses. +func LookupHost(name string) (cname 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 dnserr != nil || cfg == nil { + // better error than file not found. + 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/pkg/net/dnsconfig.go b/src/pkg/net/dnsconfig.go new file mode 100644 index 000000000..e56d964f2 --- /dev/null +++ b/src/pkg/net/dnsconfig.go @@ -0,0 +1,113 @@ +// 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"; +) + +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 +} + +var _DNS_configError os.Error; + +// 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. +func _DNS_ReadConfig() (*_DNS_Config, os.Error) { + // TODO(rsc): 6g won't let me say file, err := + var file *file; + var err os.Error; + file, err = open("/etc/resolv.conf"); + if err != nil { + return nil, err + } + conf := new(_DNS_Config); + conf.servers = make([]string, 3)[0:0]; // small, but the standard limit + conf.search = make([]string, 0); + conf.ndots = 1; + conf.timeout = 1; + conf.attempts = 1; + conf.rotate = false; + 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 len(ParseIP(name)) != 0 { + 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 = make([]string, 1); + conf.search[0] = f[1]; + } else { + conf.search = make([]string, 0) + } + + case "search": // set search path to given servers + conf.search = make([]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, nil +} + diff --git a/src/pkg/net/dnsmsg.go b/src/pkg/net/dnsmsg.go new file mode 100644 index 000000000..d7a467fc6 --- /dev/null +++ b/src/pkg/net/dnsmsg.go @@ -0,0 +1,679 @@ +// 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 packet assembly. +// +// This is intended to support name resolution during net.Dial. +// It doesn't have to be blazing fast. +// +// Rather than write the usual handful of routines to pack and +// unpack every message that can appear on the wire, we use +// reflection to write a generic pack/unpack for structs and then +// use it. Thus, if in the future we need to define new message +// structs, no new pack/unpack/printing code needs to be written. +// +// The first half of this file defines the DNS message formats. +// The second half implements the conversion to and from wire format. +// A few of the structure elements have string tags to aid the +// generic pack/unpack routines. +// +// TODO(rsc) There are enough names defined in this file that they're all +// prefixed with _DNS_. Perhaps put this in its own package later. + +package net + +import ( + "fmt"; + "os"; + "reflect"; +) + +// Packet formats + +// Wire constants. +const ( + // valid _DNS_RR_Header.rrtype and _DNS_Question.qtype + _DNS_TypeA = 1; + _DNS_TypeNS = 2; + _DNS_TypeMD = 3; + _DNS_TypeMF = 4; + _DNS_TypeCNAME = 5; + _DNS_TypeSOA = 6; + _DNS_TypeMB = 7; + _DNS_TypeMG = 8; + _DNS_TypeMR = 9; + _DNS_TypeNULL = 10; + _DNS_TypeWKS = 11; + _DNS_TypePTR = 12; + _DNS_TypeHINFO = 13; + _DNS_TypeMINFO = 14; + _DNS_TypeMX = 15; + _DNS_TypeTXT = 16; + + // valid _DNS_Question.qtype only + _DNS_TypeAXFR = 252; + _DNS_TypeMAILB = 253; + _DNS_TypeMAILA = 254; + _DNS_TypeALL = 255; + + // valid _DNS_Question.qclass + _DNS_ClassINET = 1; + _DNS_ClassCSNET = 2; + _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. +type __DNS_Header struct { + id uint16; + bits uint16; + qdcount, ancount, nscount, arcount uint16; +} + +const ( + // __DNS_Header.bits + _QR = 1<<15; // query/response (response=1) + _AA = 1<<10; // authoritative + _TC = 1<<9; // truncated + _RD = 1<<8; // recursion desired + _RA = 1<<7; // recursion available +) + +// DNS queries. +type _DNS_Question struct { + name string "domain-name"; // "domain-name" specifies encoding; see packers below + qtype uint16; + qclass uint16; +} + +// DNS responses (resource records). +// There are many types of messages, +// but they all share the same header. +type _DNS_RR_Header struct { + name string "domain-name"; + rrtype uint16; + class uint16; + ttl uint32; + rdlength uint16; // length of data after header +} + +func (h *_DNS_RR_Header) Header() *_DNS_RR_Header { + return h +} + +type _DNS_RR interface { + Header() *_DNS_RR_Header +} + + +// Specific DNS RR formats for each query type. + +type _DNS_RR_CNAME struct { + _DNS_RR_Header; + cname string "domain-name"; +} + +type _DNS_RR_HINFO struct { + _DNS_RR_Header; + cpu string; + os string; +} + +type _DNS_RR_MB struct { + _DNS_RR_Header; + mb string "domain-name"; +} + +type _DNS_RR_MG struct { + _DNS_RR_Header; + mg string "domain-name"; +} + +type _DNS_RR_MINFO struct { + _DNS_RR_Header; + rmail string "domain-name"; + email string "domain-name"; +} + +type _DNS_RR_MR struct { + _DNS_RR_Header; + mr string "domain-name"; +} + +type _DNS_RR_MX struct { + _DNS_RR_Header; + pref uint16; + mx string "domain-name"; +} + +type _DNS_RR_NS struct { + _DNS_RR_Header; + ns string "domain-name"; +} + +type _DNS_RR_PTR struct { + _DNS_RR_Header; + ptr string "domain-name"; +} + +type _DNS_RR_SOA struct { + _DNS_RR_Header; + ns string "domain-name"; + mbox string "domain-name"; + serial uint32; + refresh uint32; + retry uint32; + expire uint32; + minttl uint32; +} + +type _DNS_RR_TXT struct { + _DNS_RR_Header; + txt string; // not domain name +} + +type _DNS_RR_A struct { + _DNS_RR_Header; + a uint32 "ipv4"; +} + + +// Packing and unpacking. +// +// All the packers and unpackers take a (msg []byte, off int) +// and return (off1 int, ok bool). If they return ok==false, they +// also return off1==len(msg), so that the next unpacker will +// also fail. This lets us avoid checks of ok until the end of a +// packing sequence. + +// Map of constructors for each RR wire type. +var rr_mk = map[int] func()_DNS_RR { + _DNS_TypeCNAME: func() _DNS_RR { return new(_DNS_RR_CNAME) }, + _DNS_TypeHINFO: func() _DNS_RR { return new(_DNS_RR_HINFO) }, + _DNS_TypeMB: func() _DNS_RR { return new(_DNS_RR_MB) }, + _DNS_TypeMG: func() _DNS_RR { return new(_DNS_RR_MG) }, + _DNS_TypeMINFO: func() _DNS_RR { return new(_DNS_RR_MINFO) }, + _DNS_TypeMR: func() _DNS_RR { return new(_DNS_RR_MR) }, + _DNS_TypeMX: func() _DNS_RR { return new(_DNS_RR_MX) }, + _DNS_TypeNS: func() _DNS_RR { return new(_DNS_RR_NS) }, + _DNS_TypePTR: func() _DNS_RR { return new(_DNS_RR_PTR) }, + _DNS_TypeSOA: func() _DNS_RR { return new(_DNS_RR_SOA) }, + _DNS_TypeTXT: func() _DNS_RR { return new(_DNS_RR_TXT) }, + _DNS_TypeA: func() _DNS_RR { return new(_DNS_RR_A) }, +} + +// Pack a domain name s into msg[off:]. +// Domain names are a sequence of counted strings +// split at the dots. They end with a zero-length string. +func packDomainName(s string, msg []byte, off int) (off1 int, ok bool) { + // Add trailing dot to canonicalize name. + if n := len(s); n == 0 || s[n-1] != '.' { + s += "."; + } + + // Each dot ends a segment of the name. + // We trade each dot byte for a length byte. + // There is also a trailing zero. + // Check that we have all the space we need. + tot := len(s) + 1; + if off+tot > len(msg) { + return len(msg), false + } + + // Emit sequence of counted strings, chopping at dots. + begin := 0; + for i := 0; i < len(s); i++ { + if s[i] == '.' { + if i - begin >= 1<<6 { // top two bits of length must be clear + return len(msg), false + } + msg[off] = byte(i - begin); + off++; + for j := begin; j < i; j++ { + msg[off] = s[j]; + off++; + } + begin = i+1; + } + } + msg[off] = 0; + off++; + return off, true +} + +// Unpack a domain name. +// In addition to the simple sequences of counted strings above, +// domain names are allowed to refer to strings elsewhere in the +// packet, to avoid repeating common suffixes when returning +// many entries in a single domain. The pointers are marked +// by a length byte with the top two bits set. Ignoring those +// two bits, that byte and the next give a 14 bit offset from msg[0] +// where we should pick up the trail. +// Note that if we jump elsewhere in the packet, +// we return off1 == the offset after the first pointer we found, +// which is where the next record will start. +// In theory, the pointers are only allowed to jump backward. +// We let them jump anywhere and stop jumping after a while. +func unpackDomainName(msg []byte, off int) (s string, off1 int, ok bool) { + s = ""; + ptr := 0; // number of pointers followed +Loop: + for { + if off >= len(msg) { + return "", len(msg), false + } + c := int(msg[off]); + off++; + switch c&0xC0 { + case 0x00: + if c == 0x00 { + // end of name + break Loop + } + // literal string + if off+c > len(msg) { + return "", len(msg), false + } + s += string(msg[off:off+c]) + "."; + off += c; + case 0xC0: + // pointer to somewhere else in msg. + // remember location after first ptr, + // since that's how many bytes we consumed. + // also, don't follow too many pointers -- + // maybe there's a loop. + if off >= len(msg) { + return "", len(msg), false + } + c1 := msg[off]; + off++; + if ptr == 0 { + off1 = off + } + if ptr++; ptr > 10 { + return "", len(msg), false + } + off = (c^0xC0)<<8 | int(c1); + default: + // 0x80 and 0x40 are reserved + return "", len(msg), false + } + } + if ptr == 0 { + off1 = off + } + return s, off1, true +} + +// Pack a reflect.StructValue into msg. Struct members can only be uint16, uint32, string, +// and other (often anonymous) structs. +func packStructValue(val reflect.StructValue, msg []byte, off int) (off1 int, ok bool) { + for i := 0; i < val.Len(); i++ { + fld := val.Field(i); + name, typ, tag, xxx := val.Type().(reflect.StructType).Field(i); + switch fld.Kind() { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown packing type %v", fld.Type()); + return len(msg), false; + case reflect.StructKind: + off, ok = packStructValue(fld.(reflect.StructValue), msg, off); + case reflect.Uint16Kind: + i := fld.(reflect.Uint16Value).Get(); + if off+2 > len(msg) { + return len(msg), false + } + msg[off] = byte(i>>8); + msg[off+1] = byte(i); + off += 2; + case reflect.Uint32Kind: + i := fld.(reflect.Uint32Value).Get(); + if off+4 > len(msg) { + return len(msg), false + } + msg[off] = byte(i>>24); + msg[off+1] = byte(i>>16); + msg[off+2] = byte(i>>8); + msg[off+4] = byte(i); + off += 4; + case reflect.StringKind: + // There are multiple string encodings. + // The tag distinguishes ordinary strings from domain names. + s := fld.(reflect.StringValue).Get(); + switch tag { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown string tag %v", tag); + return len(msg), false; + case "domain-name": + off, ok = packDomainName(s, msg, off); + if !ok { + return len(msg), false + } + case "": + // Counted string: 1 byte length. + if len(s) > 255 || off + 1 + len(s) > len(msg) { + return len(msg), false + } + msg[off] = byte(len(s)); + off++; + for i := 0; i < len(s); i++ { + msg[off+i] = s[i]; + } + off += len(s); + } + } + } + return off, true +} + +func packStruct(any interface{}, msg []byte, off int) (off1 int, ok bool) { + val := reflect.NewValue(any).(reflect.PtrValue).Sub().(reflect.StructValue); + off, ok = packStructValue(val, msg, off); + return off, ok +} + +// Unpack a reflect.StructValue from msg. +// Same restrictions as packStructValue. +func unpackStructValue(val reflect.StructValue, msg []byte, off int) (off1 int, ok bool) { + for i := 0; i < val.Len(); i++ { + name, typ, tag, xxx := val.Type().(reflect.StructType).Field(i); + fld := val.Field(i); + switch fld.Kind() { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown packing type %v", fld.Type()); + return len(msg), false; + case reflect.StructKind: + off, ok = unpackStructValue(fld.(reflect.StructValue), msg, off); + case reflect.Uint16Kind: + if off+2 > len(msg) { + return len(msg), false + } + i := uint16(msg[off])<<8 | uint16(msg[off+1]); + fld.(reflect.Uint16Value).Set(i); + off += 2; + case reflect.Uint32Kind: + if off+4 > len(msg) { + return len(msg), false + } + i := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3]); + fld.(reflect.Uint32Value).Set(i); + off += 4; + case reflect.StringKind: + var s string; + switch tag { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown string tag %v", tag); + return len(msg), false; + case "domain-name": + s, off, ok = unpackDomainName(msg, off); + if !ok { + return len(msg), false + } + case "": + if off >= len(msg) || off+1+int(msg[off]) > len(msg) { + return len(msg), false + } + n := int(msg[off]); + off++; + b := make([]byte, n); + for i := 0; i < n; i++ { + b[i] = msg[off+i]; + } + off += n; + s = string(b); + } + fld.(reflect.StringValue).Set(s); + } + } + return off, true +} + +func unpackStruct(any interface{}, msg []byte, off int) (off1 int, ok bool) { + val := reflect.NewValue(any).(reflect.PtrValue).Sub().(reflect.StructValue); + off, ok = unpackStructValue(val, msg, off); + return off, ok +} + +// Generic struct printer. +// Doesn't care about the string tag "domain-name", +// but does look for an "ipv4" tag on uint32 variables, +// printing them as IP addresses. +func printStructValue(val reflect.StructValue) string { + s := "{"; + for i := 0; i < val.Len(); i++ { + if i > 0 { + s += ", "; + } + name, typ, tag, xxx := val.Type().(reflect.StructType).Field(i); + fld := val.Field(i); + if name != "" && name != "?" { // BUG? Shouldn't the reflect library hide "?" ? + s += name + "="; + } + kind := fld.Kind(); + switch { + case kind == reflect.StructKind: + s += printStructValue(fld.(reflect.StructValue)); + case kind == reflect.Uint32Kind && tag == "ipv4": + i := fld.(reflect.Uint32Value).Get(); + s += fmt.Sprintf("%d.%d.%d.%d", (i>>24)&0xFF, (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF); + default: + s += fmt.Sprint(fld.Interface()) + } + } + s += "}"; + return s; +} + +func printStruct(any interface{}) string { + val := reflect.NewValue(any).(reflect.PtrValue).Sub().(reflect.StructValue); + s := printStructValue(val); + return s +} + +// Resource record packer. +func packRR(rr _DNS_RR, msg []byte, off int) (off2 int, ok bool) { + var off1 int; + // pack twice, once to find end of header + // and again to find end of packet. + // a bit inefficient but this doesn't need to be fast. + // off1 is end of header + // off2 is end of rr + off1, ok = packStruct(rr.Header(), msg, off); + off2, ok = packStruct(rr, msg, off); + if !ok { + return len(msg), false + } + // pack a third time; redo header with correct data length + rr.Header().rdlength = uint16(off2 - off1); + packStruct(rr.Header(), msg, off); + return off2, true +} + +// Resource record unpacker. +func unpackRR(msg []byte, off int) (rr _DNS_RR, off1 int, ok bool) { + // unpack just the header, to find the rr type and length + var h _DNS_RR_Header; + off0 := off; + if off, ok = unpackStruct(&h, msg, off); !ok { + return nil, len(msg), false + } + end := off+int(h.rdlength); + + // make an rr of that type and re-unpack. + // again inefficient but doesn't need to be fast. + mk, known := rr_mk[int(h.rrtype)]; + if !known { + return &h, end, true + } + rr = mk(); + off, ok = unpackStruct(rr, msg, off0); + if off != end { + return &h, end, true + } + return rr, off, ok +} + +// Usable representation of a DNS packet. + +// A manually-unpacked version of (id, bits). +// This is in its own struct for easy printing. +type __DNS_Msg_Top struct { + id uint16; + response bool; + opcode int; + authoritative bool; + truncated bool; + recursion_desired bool; + recursion_available bool; + rcode int; +} + +type _DNS_Msg struct { + __DNS_Msg_Top; + question []_DNS_Question; + answer []_DNS_RR; + ns []_DNS_RR; + extra []_DNS_RR; +} + + +func (dns *_DNS_Msg) Pack() (msg []byte, ok bool) { + var dh __DNS_Header; + + // Convert convenient _DNS_Msg into wire-like __DNS_Header. + dh.id = dns.id; + dh.bits = uint16(dns.opcode)<<11 | uint16(dns.rcode); + if dns.recursion_available { + dh.bits |= _RA; + } + if dns.recursion_desired { + dh.bits |= _RD; + } + if dns.truncated { + dh.bits |= _TC; + } + if dns.authoritative { + dh.bits |= _AA; + } + if dns.response { + dh.bits |= _QR; + } + + // Prepare variable sized arrays. + question := dns.question; + answer := dns.answer; + ns := dns.ns; + extra := dns.extra; + + dh.qdcount = uint16(len(question)); + dh.ancount = uint16(len(answer)); + dh.nscount = uint16(len(ns)); + dh.arcount = uint16(len(extra)); + + // Could work harder to calculate message size, + // but this is far more than we need and not + // big enough to hurt the allocator. + msg = make([]byte, 2000); + + // Pack it in: header and then the pieces. + off := 0; + off, ok = packStruct(&dh, msg, off); + for i := 0; i < len(question); i++ { + off, ok = packStruct(&question[i], msg, off); + } + for i := 0; i < len(answer); i++ { + off, ok = packStruct(answer[i], msg, off); + } + for i := 0; i < len(ns); i++ { + off, ok = packStruct(ns[i], msg, off); + } + for i := 0; i < len(extra); i++ { + off, ok = packStruct(extra[i], msg, off); + } + if !ok { + return nil, false + } + return msg[0:off], true +} + +func (dns *_DNS_Msg) Unpack(msg []byte) bool { + // Header. + var dh __DNS_Header; + off := 0; + var ok bool; + if off, ok = unpackStruct(&dh, msg, off); !ok { + return false + } + dns.id = dh.id; + dns.response = (dh.bits & _QR) != 0; + dns.opcode = int(dh.bits >> 11) & 0xF; + dns.authoritative = (dh.bits & _AA) != 0; + dns.truncated = (dh.bits & _TC) != 0; + dns.recursion_desired = (dh.bits & _RD) != 0; + dns.recursion_available = (dh.bits & _RA) != 0; + dns.rcode = int(dh.bits & 0xF); + + // Arrays. + dns.question = make([]_DNS_Question, dh.qdcount); + dns.answer = make([]_DNS_RR, dh.ancount); + dns.ns = make([]_DNS_RR, dh.nscount); + dns.extra = make([]_DNS_RR, dh.arcount); + + for i := 0; i < len(dns.question); i++ { + off, ok = unpackStruct(&dns.question[i], msg, off); + } + for i := 0; i < len(dns.answer); i++ { + dns.answer[i], off, ok = unpackRR(msg, off); + } + for i := 0; i < len(dns.ns); i++ { + dns.ns[i], off, ok = unpackRR(msg, off); + } + for i := 0; i < len(dns.extra); i++ { + dns.extra[i], off, ok = unpackRR(msg, off); + } + if !ok { + return false + } +// if off != len(msg) { +// println("extra bytes in dns packet", off, "<", len(msg)); +// } + return true +} + +func (dns *_DNS_Msg) String() string { + s := "DNS: "+printStruct(&dns.__DNS_Msg_Top)+"\n"; + if len(dns.question) > 0 { + s += "-- Questions\n"; + for i := 0; i < len(dns.question); i++ { + s += printStruct(&dns.question[i])+"\n"; + } + } + if len(dns.answer) > 0 { + s += "-- Answers\n"; + for i := 0; i < len(dns.answer); i++ { + s += printStruct(dns.answer[i])+"\n"; + } + } + if len(dns.ns) > 0 { + s += "-- Name servers\n"; + for i := 0; i < len(dns.ns); i++ { + s += printStruct(dns.ns[i])+"\n"; + } + } + if len(dns.extra) > 0 { + s += "-- Extra\n"; + for i := 0; i < len(dns.extra); i++ { + s += printStruct(dns.extra[i])+"\n"; + } + } + return s; +} diff --git a/src/pkg/net/fd.go b/src/pkg/net/fd.go new file mode 100644 index 000000000..9404ed0bd --- /dev/null +++ b/src/pkg/net/fd.go @@ -0,0 +1,429 @@ +// 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. + +// TODO(rsc): All the prints in this file should go to standard error. + +package net + +import ( + "net"; + "once"; + "os"; + "sync"; + "syscall"; +) + +// Network file descriptor. +type netFD struct { + // immutable until Close + fd int; + file *os.File; + cr chan *netFD; + cw chan *netFD; + net string; + laddr string; + raddr string; + + // owned by client + rdeadline_delta int64; + rdeadline int64; + rio sync.Mutex; + wdeadline_delta int64; + wdeadline int64; + wio sync.Mutex; + + // owned by fd wait server + ncr, ncw int; +} + +// A pollServer helps FDs determine when to retry a non-blocking +// read or write after they get EAGAIN. When an FD needs to wait, +// send the fd on s.cr (for a read) or s.cw (for a write) to pass the +// request to the poll server. Then receive on fd.cr/fd.cw. +// When the pollServer finds that i/o on FD should be possible +// again, it will send fd on fd.cr/fd.cw to wake any waiting processes. +// This protocol is implemented as s.WaitRead() and s.WaitWrite(). +// +// There is one subtlety: when sending on s.cr/s.cw, the +// poll server is probably in a system call, waiting for an fd +// to become ready. It's not looking at the request channels. +// To resolve this, the poll server waits not just on the FDs it has +// been given but also its own pipe. After sending on the +// buffered channel s.cr/s.cw, WaitRead/WaitWrite writes a +// byte to the pipe, causing the pollServer's poll system call to +// return. In response to the pipe being readable, the pollServer +// re-polls its request channels. +// +// Note that the ordering is "send request" and then "wake up server". +// If the operations were reversed, there would be a race: the poll +// server might wake up and look at the request channel, see that it +// was empty, and go back to sleep, all before the requester managed +// to send the request. Because the send must complete before the wakeup, +// the request channel must be buffered. A buffer of size 1 is sufficient +// for any request load. If many processes are trying to submit requests, +// one will succeed, the pollServer will read the request, and then the +// channel will be empty for the next process's request. A larger buffer +// might help batch requests. + +type pollServer struct { + cr, cw chan *netFD; // buffered >= 1 + pr, pw *os.File; + pending map[int] *netFD; + poll *pollster; // low-level OS hooks + deadline int64; // next deadline (nsec since 1970) +} +func (s *pollServer) Run(); + +func newPollServer() (s *pollServer, err os.Error) { + s = new(pollServer); + s.cr = make(chan *netFD, 1); + s.cw = make(chan *netFD, 1); + if s.pr, s.pw, err = os.Pipe(); err != nil { + return nil, err + } + var e int; + if e = syscall.SetNonblock(s.pr.Fd(), true); e != 0 { + Errno: + err = os.ErrnoToError(e); + Error: + s.pr.Close(); + s.pw.Close(); + return nil, err; + } + if e = syscall.SetNonblock(s.pw.Fd(), true); e != 0 { + goto Errno; + } + if s.poll, err = newpollster(); err != nil { + goto Error; + } + if err = s.poll.AddFD(s.pr.Fd(), 'r', true); err != nil { + s.poll.Close(); + goto Error + } + s.pending = make(map[int] *netFD); + go s.Run(); + return s, nil +} + +func (s *pollServer) AddFD(fd *netFD, mode int) { + // TODO(rsc): This check handles a race between + // one goroutine reading and another one closing, + // but it doesn't solve the race completely: + // it still could happen that one goroutine closes + // but we read fd.fd before it does, and then + // another goroutine creates a new open file with + // that fd, which we'd now be referring to. + // The fix is probably to send the Close call + // through the poll server too, except that + // not all Reads and Writes go through the poll + // server even now. + intfd := fd.fd; + if intfd < 0 { + // fd closed underfoot + if mode == 'r' { + fd.cr <- fd + } else { + fd.cw <- fd + } + return + } + if err := s.poll.AddFD(intfd, mode, false); err != nil { + panicln("pollServer AddFD ", intfd, ": ", err.String(), "\n"); + return + } + + var t int64; + key := intfd << 1; + if mode == 'r' { + fd.ncr++; + t = fd.rdeadline; + } else { + fd.ncw++; + key++; + t = fd.wdeadline; + } + s.pending[key] = fd; + if t > 0 && (s.deadline == 0 || t < s.deadline) { + s.deadline = t; + } +} + +func (s *pollServer) LookupFD(fd int, mode int) *netFD { + key := fd << 1; + if mode == 'w' { + key++; + } + netfd, ok := s.pending[key]; + if !ok { + return nil + } + s.pending[key] = nil, false; + return netfd +} + +func (s *pollServer) WakeFD(fd *netFD, mode int) { + if mode == 'r' { + for fd.ncr > 0 { + fd.ncr--; + fd.cr <- fd + } + } else { + for fd.ncw > 0 { + fd.ncw--; + fd.cw <- fd + } + } +} + +func (s *pollServer) Now() int64 { + sec, nsec, err := os.Time(); + if err != nil { + panic("net: os.Time: ", err.String()); + } + nsec += sec * 1e9; + return nsec; +} + +func (s *pollServer) CheckDeadlines() { + now := s.Now(); + // TODO(rsc): This will need to be handled more efficiently, + // probably with a heap indexed by wakeup time. + + var next_deadline int64; + for key, fd := range s.pending { + var t int64; + var mode int; + if key&1 == 0 { + mode = 'r'; + } else { + mode = 'w'; + } + if mode == 'r' { + t = fd.rdeadline; + } else { + t = fd.wdeadline; + } + if t > 0 { + if t <= now { + s.pending[key] = nil, false; + if mode == 'r' { + s.poll.DelFD(fd.fd, mode); + fd.rdeadline = -1; + } else { + s.poll.DelFD(fd.fd, mode); + fd.wdeadline = -1; + } + s.WakeFD(fd, mode); + } else if next_deadline == 0 || t < next_deadline { + next_deadline = t; + } + } + } + s.deadline = next_deadline; +} + +func (s *pollServer) Run() { + var scratch [100]byte; + for { + var t = s.deadline; + if t > 0 { + t = t - s.Now(); + if t < 0 { + s.CheckDeadlines(); + continue; + } + } + fd, mode, err := s.poll.WaitFD(t); + if err != nil { + print("pollServer WaitFD: ", err.String(), "\n"); + return + } + if fd < 0 { + // Timeout happened. + s.CheckDeadlines(); + continue; + } + if fd == s.pr.Fd() { + // Drain our wakeup pipe. + for nn, e := s.pr.Read(&scratch); nn > 0; { + nn, e = s.pr.Read(&scratch) + } + + // Read from channels + for fd, ok := <-s.cr; ok; fd, ok = <-s.cr { + s.AddFD(fd, 'r') + } + for fd, ok := <-s.cw; ok; fd, ok = <-s.cw { + s.AddFD(fd, 'w') + } + } else { + netfd := s.LookupFD(fd, mode); + if netfd == nil { + print("pollServer: unexpected wakeup for fd=", netfd, " mode=", string(mode), "\n"); + continue + } + s.WakeFD(netfd, mode); + } + } +} + +var wakeupbuf [1]byte; +func (s *pollServer) Wakeup() { + s.pw.Write(&wakeupbuf) +} + +func (s *pollServer) WaitRead(fd *netFD) { + s.cr <- fd; + s.Wakeup(); + <-fd.cr +} + +func (s *pollServer) WaitWrite(fd *netFD) { + s.cw <- fd; + s.Wakeup(); + <-fd.cw +} + + +// Network FD methods. +// All the network FDs use a single pollServer. + +var pollserver *pollServer + +func _StartServer() { + p, err := newPollServer(); + if err != nil { + print("Start pollServer: ", err.String(), "\n") + } + pollserver = p +} + +func newFD(fd int, net, laddr, raddr string) (f *netFD, err os.Error) { + if pollserver == nil { + once.Do(_StartServer); + } + if e := syscall.SetNonblock(fd, true); e != 0 { + return nil, os.ErrnoToError(e); + } + f = new(netFD); + f.fd = fd; + f.net = net; + f.laddr = laddr; + f.raddr = raddr; + f.file = os.NewFile(fd, "net: " + net + " " + laddr + " " + raddr); + f.cr = make(chan *netFD, 1); + f.cw = make(chan *netFD, 1); + return f, nil +} + +func (fd *netFD) Close() os.Error { + if fd == nil || fd.file == nil { + return os.EINVAL + } + + // In case the user has set linger, + // switch to blocking mode so the close blocks. + // As long as this doesn't happen often, + // we can handle the extra OS processes. + // Otherwise we'll need to use the pollserver + // for Close too. Sigh. + syscall.SetNonblock(fd.file.Fd(), false); + + e := fd.file.Close(); + fd.file = nil; + fd.fd = -1; + return e +} + +func (fd *netFD) Read(p []byte) (n int, err os.Error) { + if fd == nil || fd.file == nil { + return -1, os.EINVAL + } + fd.rio.Lock(); + defer fd.rio.Unlock(); + if fd.rdeadline_delta > 0 { + fd.rdeadline = pollserver.Now() + fd.rdeadline_delta; + } else { + fd.rdeadline = 0; + } + n, err = fd.file.Read(p); + for err == os.EAGAIN && fd.rdeadline >= 0 { + pollserver.WaitRead(fd); + n, err = fd.file.Read(p) + } + return n, err +} + +func (fd *netFD) Write(p []byte) (n int, err os.Error) { + if fd == nil || fd.file == nil { + return -1, os.EINVAL + } + fd.wio.Lock(); + defer fd.wio.Unlock(); + if fd.wdeadline_delta > 0 { + fd.wdeadline = pollserver.Now() + fd.wdeadline_delta; + } else { + fd.wdeadline = 0; + } + err = nil; + nn := 0; + for nn < len(p) { + n, err = fd.file.Write(p[nn:len(p)]); + if n > 0 { + nn += n + } + if nn == len(p) { + break; + } + if err == os.EAGAIN && fd.wdeadline >= 0 { + pollserver.WaitWrite(fd); + continue; + } + if n == 0 || err != nil { + break; + } + } + return nn, err +} + +func sockaddrToString(sa syscall.Sockaddr) (name string, err os.Error) + +func (fd *netFD) accept() (nfd *netFD, err os.Error) { + if fd == nil || fd.file == nil { + return nil, os.EINVAL + } + + // See ../syscall/exec.go for description of ForkLock. + // It is okay to hold the lock across syscall.Accept + // because we have put fd.fd into non-blocking mode. + syscall.ForkLock.RLock(); + var s, e int; + var sa syscall.Sockaddr; + for { + s, sa, e = syscall.Accept(fd.fd); + if e != syscall.EAGAIN { + break; + } + syscall.ForkLock.RUnlock(); + pollserver.WaitRead(fd); + syscall.ForkLock.RLock(); + } + if e != 0 { + syscall.ForkLock.RUnlock(); + return nil, os.ErrnoToError(e) + } + syscall.CloseOnExec(s); + syscall.ForkLock.RUnlock(); + + raddr, err1 := sockaddrToString(sa); + if err1 != nil { + raddr = "invalid-address"; + } + if nfd, err = newFD(s, fd.net, fd.laddr, raddr); err != nil { + syscall.Close(s); + return nil, err + } + return nfd, nil +} + diff --git a/src/pkg/net/fd_darwin.go b/src/pkg/net/fd_darwin.go new file mode 100644 index 000000000..42bf51221 --- /dev/null +++ b/src/pkg/net/fd_darwin.go @@ -0,0 +1,115 @@ +// 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. + +// Waiting for FDs via kqueue/kevent. + +package net + +import ( + "net"; + "os"; + "syscall"; +) + +var kqueuePhaseError = &Error{"kqueue phase error"} + +type pollster struct { + kq int; + eventbuf [10]syscall.Kevent_t; + events []syscall.Kevent_t; +} + +func newpollster() (p *pollster, err os.Error) { + p = new(pollster); + var e int; + if p.kq, e = syscall.Kqueue(); e != 0 { + return nil, os.ErrnoToError(e) + } + p.events = p.eventbuf[0:0]; + return p, nil +} + +func (p *pollster) AddFD(fd int, mode int, repeat bool) os.Error { + var kmode int; + if mode == 'r' { + kmode = syscall.EVFILT_READ + } else { + kmode = syscall.EVFILT_WRITE + } + var events [1]syscall.Kevent_t; + ev := &events[0]; + // EV_ADD - add event to kqueue list + // EV_RECEIPT - generate fake EV_ERROR as result of add, + // rather than waiting for real event + // EV_ONESHOT - delete the event the first time it triggers + flags := syscall.EV_ADD | syscall.EV_RECEIPT; + if !repeat { + flags |= syscall.EV_ONESHOT + } + syscall.SetKevent(ev, fd, kmode, flags); + + n, e := syscall.Kevent(p.kq, &events, &events, nil); + if e != 0 { + return os.ErrnoToError(e) + } + if n != 1 || (ev.Flags & syscall.EV_ERROR) == 0 || int(ev.Ident) != fd || int(ev.Filter) != kmode { + return kqueuePhaseError + } + if ev.Data != 0 { + return os.ErrnoToError(int(ev.Data)) + } + return nil +} + +func (p *pollster) DelFD(fd int, mode int) { + var kmode int; + if mode == 'r' { + kmode = syscall.EVFILT_READ + } else { + kmode = syscall.EVFILT_WRITE + } + var events [1]syscall.Kevent_t; + ev := &events[0]; + // EV_DELETE - delete event from kqueue list + // EV_RECEIPT - generate fake EV_ERROR as result of add, + // rather than waiting for real event + syscall.SetKevent(ev, fd, kmode, syscall.EV_DELETE | syscall.EV_RECEIPT); + syscall.Kevent(p.kq, &events, &events, nil); +} + +func (p *pollster) WaitFD(nsec int64) (fd int, mode int, err os.Error) { + var t *syscall.Timespec; + for len(p.events) == 0 { + if nsec > 0 { + if t == nil { + t = new(syscall.Timespec); + } + *t = syscall.NsecToTimespec(nsec); + } + nn, e := syscall.Kevent(p.kq, nil, &p.eventbuf, t); + if e != 0 { + if e == syscall.EINTR { + continue + } + return -1, 0, os.ErrnoToError(e) + } + if nn == 0 { + return -1, 0, nil; + } + p.events = p.eventbuf[0:nn] + } + ev := &p.events[0]; + p.events = p.events[1:len(p.events)]; + fd = int(ev.Ident); + if ev.Filter == syscall.EVFILT_READ { + mode = 'r' + } else { + mode = 'w' + } + return fd, mode, nil +} + +func (p *pollster) Close() os.Error { + return os.ErrnoToError(syscall.Close(p.kq)) +} diff --git a/src/pkg/net/fd_linux.go b/src/pkg/net/fd_linux.go new file mode 100644 index 000000000..bd822589e --- /dev/null +++ b/src/pkg/net/fd_linux.go @@ -0,0 +1,150 @@ +// 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. + +// Waiting for FDs via epoll(7). + +package net + +import ( + "net"; + "os"; + "syscall"; +) + +const ( + readFlags = syscall.EPOLLIN | syscall.EPOLLRDHUP; + writeFlags = syscall.EPOLLOUT +) + +type pollster struct { + epfd int; + + // Events we're already waiting for + events map[int] uint32; +} + +func newpollster() (p *pollster, err os.Error) { + p = new(pollster); + var e int; + + // The arg to epoll_create is a hint to the kernel + // about the number of FDs we will care about. + // We don't know. + if p.epfd, e = syscall.EpollCreate(16); e != 0 { + return nil, os.ErrnoToError(e) + } + p.events = make(map[int] uint32); + return p, nil +} + +func (p *pollster) AddFD(fd int, mode int, repeat bool) os.Error { + var ev syscall.EpollEvent; + var already bool; + ev.Fd = int32(fd); + ev.Events, already = p.events[fd]; + if !repeat { + ev.Events |= syscall.EPOLLONESHOT; + } + if mode == 'r' { + ev.Events |= readFlags; + } else { + ev.Events |= writeFlags; + } + + var op int; + if already { + op = syscall.EPOLL_CTL_MOD; + } else { + op = syscall.EPOLL_CTL_ADD; + } + if e := syscall.EpollCtl(p.epfd, op, fd, &ev); e != 0 { + return os.ErrnoToError(e) + } + p.events[fd] = ev.Events; + return nil +} + +func (p *pollster) StopWaiting(fd int, bits uint) { + events, already := p.events[fd]; + if !already { + print("Epoll unexpected fd=", fd, "\n"); + return; + } + + // If syscall.EPOLLONESHOT is not set, the wait + // is a repeating wait, so don't change it. + if events & syscall.EPOLLONESHOT == 0 { + return; + } + + // Disable the given bits. + // If we're still waiting for other events, modify the fd + // event in the kernel. Otherwise, delete it. + events &= ^uint32(bits); + if int32(events) & ^syscall.EPOLLONESHOT != 0 { + var ev syscall.EpollEvent; + ev.Fd = int32(fd); + ev.Events = events; + if e := syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_MOD, fd, &ev); e != 0 { + print("Epoll modify fd=", fd, ": ", os.ErrnoToError(e).String(), "\n"); + } + p.events[fd] = events; + } else { + if e := syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_DEL, fd, nil); e != 0 { + print("Epoll delete fd=", fd, ": ", os.ErrnoToError(e).String(), "\n"); + } + p.events[fd] = 0, false; + } +} + +func (p *pollster) DelFD(fd int, mode int) { + if mode == 'r' { + p.StopWaiting(fd, readFlags); + } else { + p.StopWaiting(fd, writeFlags); + } +} + +func (p *pollster) WaitFD(nsec int64) (fd int, mode int, err os.Error) { + // Get an event. + var evarray [1]syscall.EpollEvent; + ev := &evarray[0]; + var msec int = -1; + if nsec > 0 { + msec = int((nsec + 1e6 - 1)/1e6); + } + n, e := syscall.EpollWait(p.epfd, &evarray, msec); + for e == syscall.EAGAIN || e == syscall.EINTR { + n, e = syscall.EpollWait(p.epfd, &evarray, msec); + } + if e != 0 { + return -1, 0, os.ErrnoToError(e); + } + if n == 0 { + return -1, 0, nil; + } + fd = int(ev.Fd); + + if ev.Events & writeFlags != 0 { + p.StopWaiting(fd, writeFlags); + return fd, 'w', nil; + } + if ev.Events & readFlags != 0 { + p.StopWaiting(fd, readFlags); + return fd, 'r', nil; + } + + // Other events are error conditions - wake whoever is waiting. + events, already := p.events[fd]; + if events & writeFlags != 0 { + p.StopWaiting(fd, writeFlags); + return fd, 'w', nil; + } + p.StopWaiting(fd, readFlags); + return fd, 'r', nil; +} + +func (p *pollster) Close() os.Error { + return os.ErrnoToError(syscall.Close(p.epfd)); +} diff --git a/src/pkg/net/ip.go b/src/pkg/net/ip.go new file mode 100644 index 000000000..774f048ca --- /dev/null +++ b/src/pkg/net/ip.go @@ -0,0 +1,421 @@ +// 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. + +// IP address manipulations +// +// IPv4 addresses are 4 bytes; IPv6 addresses are 16 bytes. +// An IPv4 address can be converted to an IPv6 address by +// adding a canonical prefix (10 zeros, 2 0xFFs). +// This library accepts either size of byte array but always +// returns 16-byte addresses. + +package net + +import ( + "net" +) + +// IP address lengths (bytes). +const ( + IPv4len = 4; + IPv6len = 16 +) + +// An IP is a single IP address, an array of bytes. +// Functions in this package accept either 4-byte (IP v4) +// or 16-byte (IP v6) arrays as input. Unless otherwise +// specified, functions in this package always return +// IP addresses in 16-byte form using the canonical +// embedding. +// +// Note that in this documentation, referring to an +// IP address as an IPv4 address or an IPv6 address +// is a semantic property of the address, not just the +// length of the byte array: a 16-byte array can still +// be an IPv4 address. +type IP []byte; + +// An IP mask is an IP address. +type IPMask []byte; + +// IPv4 returns the IP address (in 16-byte form) of the +// IPv4 address a.b.c.d. +func IPv4(a, b, c, d byte) IP { + p := make(IP, IPv6len); + for i := 0; i < 10; i++ { + p[i] = 0 + } + p[10] = 0xff; + p[11] = 0xff; + p[12] = a; + p[13] = b; + p[14] = c; + p[15] = d; + return p +} + +// Well-known IPv4 addresses +var ( + IPv4bcast = IPv4(255, 255, 255, 255); // broadcast + IPv4allsys = IPv4(224, 0, 0, 1); // all systems + IPv4allrouter = IPv4(224, 0, 0, 2); // all routers + IPv4zero = IPv4(0, 0, 0, 0); // all zeros +) + +// Well-known IPv6 addresses +var ( + IPzero = make(IP, IPv6len); // all zeros +) + +// Is p all zeros? +func isZeros(p IP) bool { + for i := 0; i < len(p); i++ { + if p[i] != 0 { + return false + } + } + return true +} + +// To4 converts the IPv4 address ip to a 4-byte representation. +// If ip is not an IPv4 address, To4 returns nil. +func (ip IP) To4() IP { + if len(ip) == IPv4len { + return ip + } + if len(ip) == IPv6len + && isZeros(ip[0:10]) + && ip[10] == 0xff + && ip[11] == 0xff { + return ip[12:16] + } + return nil +} + +// To16 converts the IP address ip to a 16-byte representation. +// If ip is not an IP address (it is the wrong length), To16 returns nil. +func (ip IP) To16() IP { + if len(ip) == IPv4len { + return IPv4(ip[0], ip[1], ip[2], ip[3]) + } + if len(ip) == IPv6len { + return ip + } + return nil +} + +// Default route masks for IPv4. +var ( + classAMask = IPMask(IPv4(0xff, 0, 0, 0)); + classBMask = IPMask(IPv4(0xff, 0xff, 0, 0)); + classCMask = IPMask(IPv4(0xff, 0xff, 0xff, 0)); +) + +// DefaultMask returns the default IP mask for the IP address ip. +// Only IPv4 addresses have default masks; DefaultMask returns +// nil if ip is not a valid IPv4 address. +func (ip IP) DefaultMask() IPMask { + if ip = ip.To4(); ip == nil { + return nil + } + switch true { + case ip[0] < 0x80: + return classAMask; + case ip[0] < 0xC0: + return classBMask; + default: + return classCMask; + } + return nil; // not reached +} + +// Mask returns the result of masking the IP address ip with mask. +func (ip IP) Mask(mask IPMask) IP { + n := len(ip); + if n != len(mask) { + return nil + } + out := make(IP, n); + for i := 0; i < n; i++ { + out[i] = ip[i] & mask[i]; + } + return out +} + +// Convert i to decimal string. +func itod(i uint) string { + if i == 0 { + return "0" + } + + // Assemble decimal in reverse order. + var b [32]byte; + bp := len(b); + for ; i > 0; i /= 10 { + bp--; + b[bp] = byte(i%10) + '0' + } + + return string(b[bp:len(b)]) +} + +// Convert i to hexadecimal string. +func itox(i uint) string { + if i == 0 { + return "0" + } + + // Assemble hexadecimal in reverse order. + var b [32]byte; + bp := len(b); + for ; i > 0; i /= 16 { + bp--; + b[bp] = "0123456789abcdef"[byte(i%16)] + } + + return string(b[bp:len(b)]) +} + +// String returns the string form of the IP address ip. +// If the address is an IPv4 address, the string representation +// is dotted decimal ("74.125.19.99"). Otherwise the representation +// is IPv6 ("2001:4860:0:2001::68"). +func (ip IP) String() string { + p := ip; + + // If IPv4, use dotted notation. + if p4 := p.To4(); len(p4) == 4 { + return itod(uint(p4[0]))+"." + +itod(uint(p4[1]))+"." + +itod(uint(p4[2]))+"." + +itod(uint(p4[3])) + } + if len(p) != IPv6len { + return "?" + } + + // Find longest run of zeros. + e0 := -1; + e1 := -1; + for i := 0; i < 16; i+=2 { + j := i; + for j < 16 && p[j] == 0 && p[j+1] == 0 { + j += 2 + } + if j > i && j - i > e1 - e0 { + e0 = i; + e1 = j + } + } + + // Print with possible :: in place of run of zeros + var s string; + for i := 0; i < 16; i += 2 { + if i == e0 { + s += "::"; + i = e1; + if i >= 16 { + break + } + } else if i > 0 { + s += ":" + } + s += itox((uint(p[i])<<8) | uint(p[i+1])) + } + return s +} + +// If mask is a sequence of 1 bits followed by 0 bits, +// return the number of 1 bits. +func simpleMaskLength(mask IPMask) int { + var i int; + for i = 0; i < len(mask); i++ { + if mask[i] != 0xFF { + break + } + } + n := 8*i; + v := mask[i]; + for v & 0x80 != 0 { + n++; + v <<= 1 + } + if v != 0 { + return -1 + } + for i++; i < len(mask); i++ { + if mask[i] != 0 { + return -1 + } + } + return n +} + +// String returns the string representation of mask. +// If the mask is in the canonical form--ones followed by zeros--the +// string representation is just the decimal number of ones. +// If the mask is in a non-canonical form, it is formatted +// as an IP address. +func (mask IPMask) String() string { + switch len(mask) { + case 4: + n := simpleMaskLength(mask); + if n >= 0 { + return itod(uint(n+(IPv6len-IPv4len)*8)) + } + case 16: + n := simpleMaskLength(mask); + if n >= 0 { + return itod(uint(n)) + } + } + return IP(mask).String(); +} + +// Parse IPv4 address (d.d.d.d). +func parseIPv4(s string) IP { + var p [IPv4len]byte; + i := 0; + for j := 0; j < IPv4len; j++ { + if j > 0 { + if s[i] != '.' { + return nil + } + i++; + } + var ( + n int; + ok bool + ) + n, i, ok = dtoi(s, i); + if !ok || n > 0xFF { + return nil + } + p[j] = byte(n) + } + if i != len(s) { + return nil + } + return IPv4(p[0], p[1], p[2], p[3]) +} + +// Parse IPv6 address. Many forms. +// The basic form is a sequence of eight colon-separated +// 16-bit hex numbers separated by colons, +// as in 0123:4567:89ab:cdef:0123:4567:89ab:cdef. +// Two exceptions: +// * A run of zeros can be replaced with "::". +// * The last 32 bits can be in IPv4 form. +// Thus, ::ffff:1.2.3.4 is the IPv4 address 1.2.3.4. +func parseIPv6(s string) IP { + p := make(IP, 16); + ellipsis := -1; // position of ellipsis in p + i := 0; // index in string s + + // Might have leading ellipsis + if len(s) >= 2 && s[0] == ':' && s[1] == ':' { + ellipsis = 0; + i = 2; + // Might be only ellipsis + if i == len(s) { + return p + } + } + + // Loop, parsing hex numbers followed by colon. + j := 0; +L: for j < IPv6len { + // Hex number. + n, i1, ok := xtoi(s, i); + if !ok || n > 0xFFFF { + return nil + } + + // If followed by dot, might be in trailing IPv4. + if i1 < len(s) && s[i1] == '.' { + if ellipsis < 0 && j != IPv6len - IPv4len { + // Not the right place. + return nil + } + if j+IPv4len > IPv6len { + // Not enough room. + return nil + } + p4 := parseIPv4(s[i:len(s)]); + if p4 == nil { + return nil + } + // BUG: p[j:j+4] = p4 + p[j] = p4[12]; + p[j+1] = p4[13]; + p[j+2] = p4[14]; + p[j+3] = p4[15]; + i = len(s); + j += 4; + break + } + + // Save this 16-bit chunk. + p[j] = byte(n>>8); + p[j+1] = byte(n); + j += 2; + + // Stop at end of string. + i = i1; + if i == len(s) { + break + } + + // Otherwise must be followed by colon and more. + if s[i] != ':' && i+1 == len(s) { + return nil + } + i++; + + // Look for ellipsis. + if s[i] == ':' { + if ellipsis >= 0 { // already have one + return nil + } + ellipsis = j; + if i++; i == len(s) { // can be at end + break + } + } + } + + // Must have used entire string. + if i != len(s) { + return nil + } + + // If didn't parse enough, expand ellipsis. + if j < IPv6len { + if ellipsis < 0 { + return nil + } + n := IPv6len - j; + for k := j-1; k >= ellipsis; k-- { + p[k+n] = p[k] + } + for k := ellipsis+n-1; k>=ellipsis; k-- { + p[k] = 0 + } + } + return p +} + +// ParseIP parses s as an IP address, returning the result. +// The string s can be in dotted decimal ("74.125.19.99") +// or IPv6 ("2001:4860:0:2001::68") form. +// If s is not a valid textual representation of an IP address, +// ParseIP returns nil. +func ParseIP(s string) IP { + p := parseIPv4(s); + if p != nil { + return p + } + return parseIPv6(s) +} + diff --git a/src/pkg/net/ip_test.go b/src/pkg/net/ip_test.go new file mode 100644 index 000000000..fb2ae8216 --- /dev/null +++ b/src/pkg/net/ip_test.go @@ -0,0 +1,50 @@ +// 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 isEqual(a, b IP) bool { + if a == nil && b == nil { + 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 IP; +} +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", + IP{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)}, +} + +func TestParseIP(t *testing.T) { + for i := 0; i < len(parseiptests); i++ { + tt := parseiptests[i]; + if out := ParseIP(tt.in); !isEqual(out, tt.out) { + t.Errorf("ParseIP(%#q) = %v, want %v", tt.in, out, tt.out); + } + } +} diff --git a/src/pkg/net/net.go b/src/pkg/net/net.go new file mode 100644 index 000000000..5c442e6a4 --- /dev/null +++ b/src/pkg/net/net.go @@ -0,0 +1,862 @@ +// 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"; + "os"; + "strconv"; + "syscall"; +) + +var ( + BadAddress os.Error = &Error{"malformed address"}; + MissingAddress os.Error = &Error{"missing address"}; + UnknownNetwork os.Error = &Error{"unknown network"}; + UnknownHost os.Error = &Error{"unknown host"}; + UnknownSocketFamily os.Error = &Error{"unknown socket family"}; +) + + +// Conn is a generic network connection. +type Conn interface { + // Read blocks until data is ready from the connection + // and then reads into b. It returns the number + // of bytes read, or 0 if the connection has been closed. + Read(b []byte) (n int, err os.Error); + + // Write writes the data in b to the connection. + Write(b []byte) (n int, err os.Error); + + // Close closes the connection. + Close() os.Error; + + // For packet-based protocols such as UDP, + // ReadFrom reads the next packet from the network, + // returning the number of bytes read and the remote + // address that sent them. + ReadFrom(b []byte) (n int, addr string, err os.Error); + + // For packet-based protocols such as UDP, + // WriteTo writes the byte buffer b to the network + // as a single payload, sending it to the target address. + WriteTo(addr string, b []byte) (n int, err os.Error); + + // SetReadBuffer sets the size of the operating system's + // receive buffer associated with the connection. + SetReadBuffer(bytes int) os.Error; + + // SetReadBuffer sets the size of the operating system's + // transmit buffer associated with the connection. + SetWriteBuffer(bytes int) os.Error; + + // SetTimeout sets the read and write deadlines associated + // with the connection. + SetTimeout(nsec int64) os.Error; + + // SetReadTimeout sets the time (in nanoseconds) that + // Read will wait for data before returning os.EAGAIN. + // Setting nsec == 0 (the default) disables the deadline. + SetReadTimeout(nsec int64) os.Error; + + // SetWriteTimeout sets the time (in nanoseconds) that + // Write will wait to send its data before returning os.EAGAIN. + // Setting nsec == 0 (the default) disables the deadline. + // Even if write times out, it may return n > 0, indicating that + // some of the data was successfully written. + SetWriteTimeout(nsec int64) os.Error; + + // SetLinger sets the behavior of Close() on a connection + // which still has data waiting to be sent or to be acknowledged. + // + // If sec < 0 (the default), Close returns immediately and + // the operating system finishes sending the data in the background. + // + // If sec == 0, Close returns immediately and the operating system + // discards any unsent or unacknowledged data. + // + // If sec > 0, Close blocks for at most sec seconds waiting for + // data to be sent and acknowledged. + SetLinger(sec int) os.Error; + + // SetReuseAddr sets whether it is okay to reuse addresses + // from recent connections that were not properly closed. + SetReuseAddr(reuseaddr bool) os.Error; + + // SetDontRoute sets whether outgoing messages should + // bypass the system routing tables. + SetDontRoute(dontroute bool) os.Error; + + // SetKeepAlive sets whether the operating system should send + // keepalive messages on the connection. + SetKeepAlive(keepalive bool) os.Error; + + // BindToDevice binds a connection to a particular network device. + BindToDevice(dev string) os.Error; +} + +// Should we try to use the IPv4 socket interface if we're +// only dealing with IPv4 sockets? As long as the host system +// understands IPv6, it's okay to pass IPv4 addresses to the IPv6 +// interface. That simplifies our code and is most general. +// Unfortunately, we need to run on kernels built without IPv6 support too. +// So probe the kernel to figure it out. +func kernelSupportsIPv6() bool { + fd, e := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); + if fd >= 0 { + syscall.Close(fd) + } + return e == 0 +} + +var preferIPv4 = !kernelSupportsIPv6() + +// TODO(rsc): if syscall.OS == "linux", we're supposd to read +// /proc/sys/net/core/somaxconn, +// to take advantage of kernels that have raised the limit. +func listenBacklog() int { + return syscall.SOMAXCONN +} + +func LookupHost(name string) (cname 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) { + // The port starts after the last colon. + var i int; + for i = len(hostport)-1; i >= 0; i-- { + if hostport[i] == ':' { + break + } + } + if i < 0 { + return "", "", BadAddress + } + + host = hostport[0:i]; + port = hostport[i+1:len(hostport)]; + + // Can put brackets around host ... + if len(host) > 0 && host[0] == '[' && host[len(host)-1] == ']' { + host = host[1:len(host)-1] + } else { + // ... but if there are no brackets, no colons. + if byteIndex(host, ':') >= 0 { + return "", "", BadAddress + } + } + return host, port, nil +} + +// Join "host" and "port" into "host:port". +// If host contains colons, will join into "[host]:port". +func joinHostPort(host, port string) string { + // If host has colons, have to bracket it. + if byteIndex(host, ':') >= 0 { + return "[" + host + "]:" + port + } + return host + ":" + port +} + +// Convert "host:port" into IP address and port. +// For now, host and port must be numeric literals. +// Eventually, we'll have name resolution. +func hostPortToIP(net, hostport, mode string) (ip IP, iport int, err os.Error) { + var host, port string; + host, port, err = splitHostPort(hostport); + if err != nil { + return nil, 0, err + } + + var addr IP; + if host == "" { + if mode == "listen" { + if preferIPv4 { + addr = IPv4zero; + } else { + addr = IPzero; // wildcard - listen to all + } + } else { + return nil, 0, MissingAddress; + } + } + + // Try as an IP address. + if addr == nil { + addr = ParseIP(host); + } + if addr == nil { + // 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 + } + } + + p, i, ok := dtoi(port, 0); + if !ok || i != len(port) { + p, err = LookupPort(net, port); + if err != nil { + return nil, 0, err + } + } + if p < 0 || p > 0xFFFF { + return nil, 0, BadAddress + } + + return addr, p, nil +} + +func sockaddrToString(sa syscall.Sockaddr) (name string, err os.Error) { + switch a := sa.(type) { + case *syscall.SockaddrInet4: + return joinHostPort(IP(&a.Addr).String(), strconv.Itoa(a.Port)), nil; + case *syscall.SockaddrInet6: + return joinHostPort(IP(&a.Addr).String(), strconv.Itoa(a.Port)), nil; + case *syscall.SockaddrUnix: + return a.Name, nil; + } + return "", UnknownSocketFamily +} + +func ipToSockaddr(family int, ip IP, port int) (syscall.Sockaddr, os.Error) { + switch family { + case syscall.AF_INET: + if ip = ip.To4(); ip == nil { + return nil, os.EINVAL + } + s := new(syscall.SockaddrInet4); + for i := 0; i < IPv4len; i++ { + s.Addr[i] = ip[i]; + } + s.Port = port; + return s, nil; + case syscall.AF_INET6: + // IPv4 callers use 0.0.0.0 to mean "announce on any available address". + // In IPv6 mode, Linux treats that as meaning "announce on 0.0.0.0", + // which it refuses to do. Rewrite to the IPv6 all zeros. + if p4 := ip.To4(); p4 != nil && p4[0] == 0 && p4[1] == 0 && p4[2] == 0 && p4[3] == 0 { + ip = IPzero; + } + if ip = ip.To16(); ip == nil { + return nil, os.EINVAL + } + s := new(syscall.SockaddrInet6); + for i := 0; i < IPv6len; i++ { + s.Addr[i] = ip[i]; + } + s.Port = port; + return s, nil; + } + return nil, os.EINVAL; +} + +// Boolean to int. +func boolint(b bool) int { + if b { + return 1 + } + return 0 +} + +// Generic socket creation. +func socket(net, laddr, raddr string, f, p, t int, la, ra syscall.Sockaddr) (fd *netFD, err os.Error) { + // See ../syscall/exec.go for description of ForkLock. + syscall.ForkLock.RLock(); + s, e := syscall.Socket(f, p, t); + if e != 0 { + syscall.ForkLock.RUnlock(); + return nil, os.ErrnoToError(e) + } + syscall.CloseOnExec(s); + syscall.ForkLock.RUnlock(); + + // Allow reuse of recently-used addresses. + syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); + + var r int64; + if la != nil { + e = syscall.Bind(s, la); + if e != 0 { + syscall.Close(s); + return nil, os.ErrnoToError(e) + } + } + + if ra != nil { + e = syscall.Connect(s, ra); + if e != 0 { + syscall.Close(s); + return nil, os.ErrnoToError(e) + } + } + + fd, err = newFD(s, net, laddr, raddr); + if err != nil { + syscall.Close(s); + return nil, err + } + + return fd, nil +} + + +// Generic implementation of Conn interface; not exported. +type connBase struct { + fd *netFD; + raddr string; +} + +func (c *connBase) File() *os.File { + if c == nil { + return nil + } + return c.fd.file; +} + +func (c *connBase) sysFD() int { + if c == nil || c.fd == nil { + return -1; + } + return c.fd.fd; +} + +func (c *connBase) Read(b []byte) (n int, err os.Error) { + n, err = c.fd.Read(b); + return n, err +} + +func (c *connBase) Write(b []byte) (n int, err os.Error) { + n, err = c.fd.Write(b); + return n, err +} + +func (c *connBase) ReadFrom(b []byte) (n int, raddr string, err os.Error) { + if c == nil { + return -1, "", os.EINVAL + } + n, err = c.Read(b); + return n, c.raddr, err +} + +func (c *connBase) WriteTo(raddr string, b []byte) (n int, err os.Error) { + if c == nil { + return -1, os.EINVAL + } + if raddr != c.raddr { + return -1, os.EINVAL + } + n, err = c.Write(b); + return n, err +} + +func (c *connBase) Close() os.Error { + if c == nil { + return os.EINVAL + } + return c.fd.Close() +} + + +func setsockoptInt(fd, level, opt int, value int) os.Error { + return os.ErrnoToError(syscall.SetsockoptInt(fd, level, opt, value)); +} + +func setsockoptNsec(fd, level, opt int, nsec int64) os.Error { + var tv = syscall.NsecToTimeval(nsec); + return os.ErrnoToError(syscall.SetsockoptTimeval(fd, level, opt, &tv)); +} + +func (c *connBase) SetReadBuffer(bytes int) os.Error { + return setsockoptInt(c.sysFD(), syscall.SOL_SOCKET, syscall.SO_RCVBUF, bytes); +} + +func (c *connBase) SetWriteBuffer(bytes int) os.Error { + return setsockoptInt(c.sysFD(), syscall.SOL_SOCKET, syscall.SO_SNDBUF, bytes); +} + +func (c *connBase) SetReadTimeout(nsec int64) os.Error { + c.fd.rdeadline_delta = nsec; + return nil; +} + +func (c *connBase) SetWriteTimeout(nsec int64) os.Error { + c.fd.wdeadline_delta = nsec; + return nil; +} + +func (c *connBase) SetTimeout(nsec int64) os.Error { + if e := c.SetReadTimeout(nsec); e != nil { + return e + } + return c.SetWriteTimeout(nsec) +} + +func (c *connBase) SetReuseAddr(reuse bool) os.Error { + return setsockoptInt(c.sysFD(), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, boolint(reuse)); +} + +func (c *connBase) BindToDevice(dev string) os.Error { + // TODO(rsc): call setsockopt with null-terminated string pointer + return os.EINVAL +} + +func (c *connBase) SetDontRoute(dontroute bool) os.Error { + return setsockoptInt(c.sysFD(), syscall.SOL_SOCKET, syscall.SO_DONTROUTE, boolint(dontroute)); +} + +func (c *connBase) SetKeepAlive(keepalive bool) os.Error { + return setsockoptInt(c.sysFD(), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, boolint(keepalive)); +} + +func (c *connBase) SetLinger(sec int) os.Error { + var l syscall.Linger; + if sec >= 0 { + l.Onoff = 1; + l.Linger = int32(sec); + } else { + l.Onoff = 0; + l.Linger = 0; + } + e := syscall.SetsockoptLinger(c.sysFD(), syscall.SOL_SOCKET, syscall.SO_LINGER, &l); + return os.ErrnoToError(e); +} + + +// Internet sockets (TCP, UDP) + +func internetSocket(net, laddr, raddr string, proto int, mode string) (fd *netFD, err os.Error) { + // Parse addresses (unless they are empty). + var lip, rip IP; + var lport, rport int; + var lerr, rerr os.Error; + + if laddr != "" { + lip, lport, lerr = hostPortToIP(net, laddr, mode); + if lerr != nil { + return nil, lerr + } + } + if raddr != "" { + rip, rport, rerr = hostPortToIP(net, raddr, mode); + if rerr != nil { + return nil, rerr + } + } + + // Figure out IP version. + // If network has a suffix like "tcp4", obey it. + vers := 0; + switch net[len(net)-1] { + case '4': + vers = 4; + case '6': + vers = 6; + default: + // Otherwise, guess. + // If the addresses are IPv4 and we prefer IPv4, use 4; else 6. + if preferIPv4 && (lip == nil || lip.To4() != nil) && (rip == nil || rip.To4() != nil) { + vers = 4 + } else { + vers = 6 + } + } + + var family int; + if vers == 4 { + family = syscall.AF_INET + } else { + family = syscall.AF_INET6 + } + + var la, ra syscall.Sockaddr; + if lip != nil { + la, lerr = ipToSockaddr(family, lip, lport); + if lerr != nil { + return nil, lerr + } + } + if rip != nil { + ra, rerr = ipToSockaddr(family, rip, rport); + if rerr != nil { + return nil, rerr + } + } + + fd, err = socket(net, laddr, raddr, family, proto, 0, la, ra); + return fd, err +} + + +// TCP connections. + +// ConnTCP is an implementation of the Conn interface +// for TCP network connections. +type ConnTCP struct { + connBase +} + +func (c *ConnTCP) SetNoDelay(nodelay bool) os.Error { + if c == nil { + return os.EINVAL + } + return setsockoptInt(c.sysFD(), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, boolint(nodelay)) +} + +func newConnTCP(fd *netFD, raddr string) *ConnTCP { + c := new(ConnTCP); + c.fd = fd; + c.raddr = raddr; + c.SetNoDelay(true); + return c +} + +// DialTCP is like Dial but can only connect to TCP networks +// and returns a ConnTCP structure. +func DialTCP(net, laddr, raddr string) (c *ConnTCP, err os.Error) { + if raddr == "" { + return nil, MissingAddress + } + fd, e := internetSocket(net, laddr, raddr, syscall.SOCK_STREAM, "dial"); + if e != nil { + return nil, e + } + return newConnTCP(fd, raddr), nil +} + + +// UDP connections. + +// TODO(rsc): UDP headers mode + +// ConnUDP is an implementation of the Conn interface +// for UDP network connections. +type ConnUDP struct { + connBase +} + +func newConnUDP(fd *netFD, raddr string) *ConnUDP { + c := new(ConnUDP); + c.fd = fd; + c.raddr = raddr; + return c +} + +// DialUDP is like Dial but can only connect to UDP networks +// and returns a ConnUDP structure. +func DialUDP(net, laddr, raddr string) (c *ConnUDP, err os.Error) { + if raddr == "" { + return nil, MissingAddress + } + fd, e := internetSocket(net, laddr, raddr, syscall.SOCK_DGRAM, "dial"); + if e != nil { + return nil, e + } + return newConnUDP(fd, raddr), nil +} + + +// TODO: raw IP connections + +// TODO: raw ethernet connections + + +// Unix domain sockets + +func unixSocket(net, laddr, raddr string, mode string) (fd *netFD, err os.Error) { + var proto int; + switch net { + default: + return nil, UnknownNetwork; + case "unix": + proto = syscall.SOCK_STREAM; + case "unix-dgram": + proto = syscall.SOCK_DGRAM; + } + + var la, ra syscall.Sockaddr; + switch mode { + case "dial": + if laddr != "" { + return nil, BadAddress; + } + if raddr == "" { + return nil, MissingAddress; + } + ra = &syscall.SockaddrUnix{Name: raddr}; + + case "listen": + if laddr == "" { + return nil, MissingAddress; + } + la = &syscall.SockaddrUnix{Name: laddr}; + if raddr != "" { + return nil, BadAddress; + } + } + + fd, err = socket(net, laddr, raddr, syscall.AF_UNIX, proto, 0, la, ra); + return fd, err +} + +// ConnUnix is an implementation of the Conn interface +// for connections to Unix domain sockets. +type ConnUnix struct { + connBase +} + +func newConnUnix(fd *netFD, raddr string) *ConnUnix { + c := new(ConnUnix); + c.fd = fd; + c.raddr = raddr; + return c; +} + +// DialUnix is like Dial but can only connect to Unix domain sockets +// and returns a ConnUnix structure. The laddr argument must be +// the empty string; it is included only to match the signature of +// the other dial routines. +func DialUnix(net, laddr, raddr string) (c *ConnUnix, err os.Error) { + fd, e := unixSocket(net, laddr, raddr, "dial"); + if e != nil { + return nil, e + } + return newConnUnix(fd, raddr), nil; +} + +// ListenerUnix is a Unix domain socket listener. +// Clients should typically use variables of type Listener +// instead of assuming Unix domain sockets. +type ListenerUnix struct { + fd *netFD; + laddr string +} + +// ListenUnix announces on the Unix domain socket laddr and returns a Unix listener. +// Net can be either "unix" (stream sockets) or "unix-dgram" (datagram sockets). +func ListenUnix(net, laddr string) (l *ListenerUnix, err os.Error) { + fd, e := unixSocket(net, laddr, "", "listen"); + if e != nil { + // Check for socket ``in use'' but ``refusing connections,'' + // which means some program created it and exited + // without unlinking it from the file system. + // Clean up on that program's behalf and try again. + // Don't do this for Linux's ``abstract'' sockets, which begin with @. + if e != os.EADDRINUSE || laddr[0] == '@' { + return nil, e; + } + fd1, e1 := unixSocket(net, "", laddr, "dial"); + if e1 == nil { + fd1.Close(); + } + if e1 != os.ECONNREFUSED { + return nil, e; + } + syscall.Unlink(laddr); + fd1, e1 = unixSocket(net, laddr, "", "listen"); + if e1 != nil { + return nil, e; + } + fd = fd1; + } + e1 := syscall.Listen(fd.fd, 8); // listenBacklog()); + if e1 != 0 { + syscall.Close(fd.fd); + return nil, os.ErrnoToError(e1); + } + return &ListenerUnix{fd, laddr}, nil; +} + +// AcceptUnix accepts the next incoming call and returns the new connection +// and the remote address. +func (l *ListenerUnix) AcceptUnix() (c *ConnUnix, raddr string, err os.Error) { + if l == nil || l.fd == nil || l.fd.fd < 0 { + return nil, "", os.EINVAL + } + fd, e := l.fd.accept(); + if e != nil { + return nil, "", e + } + return newConnUnix(fd, fd.raddr), raddr, nil +} + +// Accept implements the Accept method in the Listener interface; +// it waits for the next call and returns a generic Conn. +func (l *ListenerUnix) Accept() (c Conn, raddr string, err os.Error) { + // TODO(rsc): 6g bug prevents saying + // c, raddr, err = l.AcceptUnix(); + // return; + c1, r1, e1 := l.AcceptUnix(); + return c1, r1, e1; +} + + +// Close stops listening on the Unix address. +// Already accepted connections are not closed. +func (l *ListenerUnix) Close() os.Error { + if l == nil || l.fd == nil { + return os.EINVAL + } + + // The operating system doesn't clean up + // the file that announcing created, so + // we have to clean it up ourselves. + // There's a race here--we can't know for + // sure whether someone else has come along + // and replaced our socket name already-- + // but this sequence (remove then close) + // is at least compatible with the auto-remove + // sequence in ListenUnix. It's only non-Go + // programs that can mess us up. + if l.laddr[0] != '@' { + syscall.Unlink(l.laddr); + } + err := l.fd.Close(); + l.fd = nil; + return err; +} + +// Dial connects to the remote address raddr on the network net. +// If the string laddr is not empty, it is used as the local address +// for the connection. +// +// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), +// "udp", "udp4" (IPv4-only), and "udp6" (IPv6-only). +// +// For IP networks, addresses have the form host:port. If host is +// a literal IPv6 address, it must be enclosed in square brackets. +// +// Examples: +// Dial("tcp", "", "12.34.56.78:80") +// Dial("tcp", "", "google.com:80") +// Dial("tcp", "", "[de:ad:be:ef::ca:fe]:80") +// Dial("tcp", "127.0.0.1:123", "127.0.0.1:88") +func Dial(net, laddr, raddr string) (c Conn, err os.Error) { + switch net { + case "tcp", "tcp4", "tcp6": + c, err := DialTCP(net, laddr, raddr); + if err != nil { + return nil, err + } + return c, nil; + case "udp", "udp4", "upd6": + c, err := DialUDP(net, laddr, raddr); + return c, err; + case "unix", "unix-dgram": + c, err := DialUnix(net, laddr, raddr); + return c, err; +/* + case "ether": + c, err := DialEther(net, laddr, raddr); + return c, err; + case "ipv4": + c, err := DialIPv4(net, laddr, raddr); + return c, err; + case "ipv6": + c, err := DialIPv6(net, laddr, raddr); + return c, err +*/ + } + return nil, UnknownNetwork +} + +// A Listener is a generic network listener. +// Accept waits for the next connection and Close closes the connection. +type Listener interface { + Accept() (c Conn, raddr string, err os.Error); + Close() os.Error; +} + +// ListenerTCP is a TCP network listener. +// Clients should typically use variables of type Listener +// instead of assuming TCP. +type ListenerTCP struct { + fd *netFD; + laddr string +} + +// ListenTCP announces on the TCP address laddr and returns a TCP listener. +// Net must be "tcp", "tcp4", or "tcp6". +func ListenTCP(net, laddr string) (l *ListenerTCP, err os.Error) { + fd, e := internetSocket(net, laddr, "", syscall.SOCK_STREAM, "listen"); + if e != nil { + return nil, e + } + e1 := syscall.Listen(fd.fd, listenBacklog()); + if e1 != 0 { + syscall.Close(fd.fd); + return nil, os.ErrnoToError(e1) + } + l = new(ListenerTCP); + l.fd = fd; + return l, nil +} + +// AcceptTCP accepts the next incoming call and returns the new connection +// and the remote address. +func (l *ListenerTCP) AcceptTCP() (c *ConnTCP, raddr string, err os.Error) { + if l == nil || l.fd == nil || l.fd.fd < 0 { + return nil, "", os.EINVAL + } + fd, e := l.fd.accept(); + if e != nil { + return nil, "", e + } + return newConnTCP(fd, fd.raddr), fd.raddr, nil +} + +// Accept implements the Accept method in the Listener interface; +// it waits for the next call and returns a generic Conn. +func (l *ListenerTCP) Accept() (c Conn, raddr string, err os.Error) { + c1, r1, e1 := l.AcceptTCP(); + if e1 != nil { + return nil, "", e1 + } + return c1, r1, nil +} + +// Close stops listening on the TCP address. +// Already Accepted connections are not closed. +func (l *ListenerTCP) Close() os.Error { + if l == nil || l.fd == nil { + return os.EINVAL + } + return l.fd.Close() +} + +// Listen announces on the local network address laddr. +// The network string net must be "tcp", "tcp4", "tcp6", +// "unix", or "unix-dgram". +func Listen(net, laddr string) (l Listener, err os.Error) { + switch net { + case "tcp", "tcp4", "tcp6": + l, err := ListenTCP(net, laddr); + if err != nil { + return nil, err; + } + return l, nil; + case "unix", "unix-dgram": + l, err := ListenUnix(net, laddr); + if err != nil { + return nil, err; + } + return l, nil; +/* + more here +*/ + // BUG(rsc): Listen should support UDP. + } + return nil, UnknownNetwork +} + diff --git a/src/pkg/net/parse.go b/src/pkg/net/parse.go new file mode 100644 index 000000000..de47cb812 --- /dev/null +++ b/src/pkg/net/parse.go @@ -0,0 +1,160 @@ +// 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"; +) + +type Error struct { + os.ErrorString +} + +type file struct { + file *os.File; + data []byte; +} + +func (f *file) close() { + f.file.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.FullRead(f.file, f.data[ln:cap(f.data)]); + if n >= 0 { + f.data = f.data[0:ln+n]; + } + } + s, ok = f.getLineFromData(); + return +} + +func open(name string) (*file, os.Error) { + fd, err := os.Open(name, os.O_RDONLY, 0); + if err != nil { + return nil, err; + } + return &file{fd, make([]byte, 1024)[0:0]}, nil; +} + +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. +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. +func splitAtBytes(s string, t string) []string { + a := make([]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]; +} + +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. +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. +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/pkg/net/parse_test.go b/src/pkg/net/parse_test.go new file mode 100644 index 000000000..ce0bb4709 --- /dev/null +++ b/src/pkg/net/parse_test.go @@ -0,0 +1,44 @@ +// 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"; +) + +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 := bufio.NewReader(fd); + + var file *file; + file, err = 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/pkg/net/port.go b/src/pkg/net/port.go new file mode 100644 index 000000000..21e3b48aa --- /dev/null +++ b/src/pkg/net/port.go @@ -0,0 +1,77 @@ +// 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"; +) + +// The error returned by LookupPort when a network service +// is not listed in the database. +var ErrNoService = &Error{"unknown network service"}; + +var services map[string] map[string] int +var servicesError os.Error + +func readServices() { + services = make(map[string] map[string] int); + var file *file; + file, servicesError = 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 = make(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(); +} + +// LookupPort looks up the port for the given network and service. +func LookupPort(network, service string) (port int, err os.Error) { + once.Do(readServices); + + switch network { + case "tcp4", "tcp6": + network = "tcp"; + case "udp4", "udp6": + network = "udp"; + } + + m, ok := services[network]; + if !ok { + return 0, ErrNoService; + } + port, ok = m[service]; + if !ok { + return 0, ErrNoService; + } + return port, nil; +} diff --git a/src/pkg/net/port_test.go b/src/pkg/net/port_test.go new file mode 100644 index 000000000..c535d4caa --- /dev/null +++ b/src/pkg/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 }, +} + +func TestLookupPort(t *testing.T) { + for i := 0; i < len(porttests); i++ { + tt := porttests[i]; + if port, err := LookupPort(tt.netw, tt.name); port != tt.port || (err == nil) != tt.ok { + t.Errorf("LookupPort(%q, %q) = %v, %s; want %v", + tt.netw, tt.name, port, err, tt.port); + } + } +} diff --git a/src/pkg/net/server_test.go b/src/pkg/net/server_test.go new file mode 100644 index 000000000..586b55365 --- /dev/null +++ b/src/pkg/net/server_test.go @@ -0,0 +1,93 @@ +// 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 ( + "io"; + "net"; + "os"; + "syscall"; + "testing"; +) + +func runEcho(fd io.ReadWriter, 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 runServe(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 := make(chan int); + go runEcho(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 := make(chan int); + done := make(chan int); + go runServe(t, network, listenaddr, listening, done); + <-listening; // wait for server to start + connect(t, network, dialaddr); + <-done; // make sure server stopped +} + +func TestTcpServer(t *testing.T) { + doTest(t, "tcp", "0.0.0.0:9997", "127.0.0.1:9997"); + doTest(t, "tcp", "[::]:9997", "[::ffff:127.0.0.1]:9997"); + doTest(t, "tcp", "[::]:9997", "127.0.0.1:9997"); + doTest(t, "tcp", ":9997", "127.0.0.1:9997"); + doTest(t, "tcp", "0.0.0.0:9997", "[::ffff:127.0.0.1]:9997"); +} + +func TestUnixServer(t *testing.T) { + doTest(t, "unix", "/tmp/gotest.net", "/tmp/gotest.net"); + if syscall.OS == "linux" { + // Test abstract unix domain socket, a Linux-ism + doTest(t, "unix", "@gotest/net", "@gotest/net"); + } +} diff --git a/src/pkg/net/timeout_test.go b/src/pkg/net/timeout_test.go new file mode 100644 index 000000000..e08ce88ce --- /dev/null +++ b/src/pkg/net/timeout_test.go @@ -0,0 +1,42 @@ +// 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"; + "os"; + "testing"; + "time"; +) + +func testTimeout(t *testing.T, network, addr string) { + fd, err := net.Dial(network, "", addr); + defer fd.Close(); + if err != nil { + t.Errorf("dial %s %s failed: %v", network, addr, err); + } + t0 := time.Nanoseconds(); + fd.SetReadTimeout(1e8); // 100ms + var b [100]byte; + n, err1 := fd.Read(&b); + t1 := time.Nanoseconds(); + if n != 0 || err1 != os.EAGAIN { + t.Errorf("fd.Read on %s %s did not return 0, EAGAIN: %v, %v", network, addr, n, err1); + } + if t1 - t0 < 0.5e8 || t1 - t0 > 1.5e8 { + t.Errorf("fd.Read on %s %s took %f seconds, expected 0.1", network, addr, float64(t1 - t0) / 1e9); + } +} + +func TestTimeoutUDP(t *testing.T) { + testTimeout(t, "udp", "127.0.0.1:53"); +} + +func TestTimeoutTCP(t *testing.T) { + // 74.125.19.99 is www.google.com. + // could use dns, but dns depends on + // timeouts and this is the timeout test. + testTimeout(t, "tcp", "74.125.19.99:80"); +} |