From fabcaa8df3d6eb852b87821ef090d31d222870b7 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 21 Nov 2012 07:03:38 +0000 Subject: libgo: Update to current version of master library. From-SVN: r193688 --- libgo/go/net/conn_test.go | 103 +++++++++ libgo/go/net/dial.go | 77 ++++--- libgo/go/net/dial_test.go | 80 ++++++- libgo/go/net/dialgoogle_test.go | 7 +- libgo/go/net/dnsclient.go | 7 +- libgo/go/net/dnsmsg.go | 2 +- libgo/go/net/fd_plan9.go | 115 ++++++++++ libgo/go/net/fd_unix.go | 51 +++-- libgo/go/net/fd_unix_test.go | 60 ++++++ libgo/go/net/fd_windows.go | 183 +++++++++------- libgo/go/net/http/cgi/testdata/test.cgi | 63 ++++-- libgo/go/net/http/chunked.go | 13 +- libgo/go/net/http/client.go | 50 ++--- libgo/go/net/http/client_test.go | 12 +- libgo/go/net/http/cookie.go | 2 +- libgo/go/net/http/cookie_test.go | 2 +- libgo/go/net/http/fs_test.go | 2 + libgo/go/net/http/httputil/chunked.go | 13 +- libgo/go/net/http/jar.go | 17 +- libgo/go/net/http/proxy_test.go | 2 +- libgo/go/net/http/request_test.go | 2 +- libgo/go/net/http/serve_test.go | 10 +- libgo/go/net/http/server.go | 40 ++-- libgo/go/net/http/transfer.go | 13 +- libgo/go/net/http/transfer_test.go | 37 ++++ libgo/go/net/http/transport.go | 29 ++- libgo/go/net/ipraw_test.go | 32 ++- libgo/go/net/iprawsock.go | 14 +- libgo/go/net/iprawsock_plan9.go | 4 + libgo/go/net/iprawsock_posix.go | 13 +- libgo/go/net/ipsock.go | 13 +- libgo/go/net/ipsock_plan9.go | 197 ++--------------- libgo/go/net/ipsock_posix.go | 9 +- libgo/go/net/lookup.go | 46 ++++ libgo/go/net/lookup_plan9.go | 15 ++ libgo/go/net/lookup_test.go | 14 ++ libgo/go/net/lookup_unix.go | 13 ++ libgo/go/net/lookup_windows.go | 50 ++++- libgo/go/net/multicast_posix_test.go | 23 +- libgo/go/net/net.go | 101 +++++++++ libgo/go/net/net_posix.go | 110 ---------- libgo/go/net/net_test.go | 39 ++++ libgo/go/net/packetconn_test.go | 161 ++++++++++++++ libgo/go/net/protoconn_test.go | 372 ++++++++++++++++++++++++++++++++ libgo/go/net/rpc/client.go | 2 +- libgo/go/net/rpc/server.go | 67 ++++-- libgo/go/net/rpc/server_test.go | 38 +++- libgo/go/net/sendfile_windows.go | 4 +- libgo/go/net/sock_posix.go | 26 ++- libgo/go/net/tcp_test.go | 30 +++ libgo/go/net/tcpsock.go | 8 +- libgo/go/net/tcpsock_plan9.go | 127 +++++++++-- libgo/go/net/tcpsock_posix.go | 60 ++++-- libgo/go/net/textproto/reader.go | 85 +++++++- libgo/go/net/textproto/reader_test.go | 83 ++++++- libgo/go/net/timeout_test.go | 111 ++++++++++ libgo/go/net/udp_test.go | 30 +++ libgo/go/net/udpsock.go | 11 +- libgo/go/net/udpsock_plan9.go | 44 ++-- libgo/go/net/udpsock_posix.go | 17 +- libgo/go/net/unicast_posix_test.go | 6 +- libgo/go/net/unixsock_plan9.go | 4 + libgo/go/net/unixsock_posix.go | 18 +- 63 files changed, 2306 insertions(+), 683 deletions(-) create mode 100644 libgo/go/net/conn_test.go create mode 100644 libgo/go/net/fd_plan9.go create mode 100644 libgo/go/net/fd_unix_test.go create mode 100644 libgo/go/net/http/transfer_test.go delete mode 100644 libgo/go/net/net_posix.go create mode 100644 libgo/go/net/packetconn_test.go create mode 100644 libgo/go/net/protoconn_test.go (limited to 'libgo/go/net') diff --git a/libgo/go/net/conn_test.go b/libgo/go/net/conn_test.go new file mode 100644 index 0000000..037ce80 --- /dev/null +++ b/libgo/go/net/conn_test.go @@ -0,0 +1,103 @@ +// Copyright 2012 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_test + +import ( + "net" + "os" + "runtime" + "testing" + "time" +) + +var connTests = []struct { + net string + addr string +}{ + {"tcp", "127.0.0.1:0"}, + {"unix", "/tmp/gotest.net"}, + {"unixpacket", "/tmp/gotest.net"}, +} + +func TestConnAndListener(t *testing.T) { + for _, tt := range connTests { + switch tt.net { + case "unix", "unixpacket": + switch runtime.GOOS { + case "plan9", "windows": + continue + } + if tt.net == "unixpacket" && runtime.GOOS != "linux" { + continue + } + os.Remove(tt.addr) + } + + ln, err := net.Listen(tt.net, tt.addr) + if err != nil { + t.Errorf("net.Listen failed: %v", err) + return + } + ln.Addr() + defer ln.Close() + + done := make(chan int) + go transponder(t, ln, done) + + c, err := net.Dial(tt.net, ln.Addr().String()) + if err != nil { + t.Errorf("net.Dial failed: %v", err) + return + } + c.LocalAddr() + c.RemoteAddr() + c.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c.Close() + + if _, err := c.Write([]byte("CONN TEST")); err != nil { + t.Errorf("net.Conn.Write failed: %v", err) + return + } + rb := make([]byte, 128) + if _, err := c.Read(rb); err != nil { + t.Errorf("net.Conn.Read failed: %v", err) + } + + <-done + switch tt.net { + case "unix", "unixpacket": + os.Remove(tt.addr) + } + } +} + +func transponder(t *testing.T, ln net.Listener, done chan<- int) { + defer func() { done <- 1 }() + + c, err := ln.Accept() + if err != nil { + t.Errorf("net.Listener.Accept failed: %v", err) + return + } + c.LocalAddr() + c.RemoteAddr() + c.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c.Close() + + b := make([]byte, 128) + n, err := c.Read(b) + if err != nil { + t.Errorf("net.Conn.Read failed: %v", err) + return + } + if _, err := c.Write(b[:n]); err != nil { + t.Errorf("net.Conn.Write failed: %v", err) + return + } +} diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index 752f81b..a85e3c6 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -5,6 +5,7 @@ package net import ( + "runtime" "time" ) @@ -36,7 +37,7 @@ func parseDialNetwork(net string) (afnet string, proto int, err error) { return "", 0, UnknownNetworkError(net) } -func resolveNetAddr(op, net, addr string) (afnet string, a Addr, err error) { +func resolveNetAddr(op, net, addr string, deadline time.Time) (afnet string, a Addr, err error) { afnet, _, err = parseDialNetwork(net) if err != nil { return "", nil, &OpError{op, net, nil, err} @@ -44,25 +45,25 @@ func resolveNetAddr(op, net, addr string) (afnet string, a Addr, err error) { if op == "dial" && addr == "" { return "", nil, &OpError{op, net, nil, errMissingAddress} } + a, err = resolveAfnetAddr(afnet, addr, deadline) + return +} + +func resolveAfnetAddr(afnet, addr string, deadline time.Time) (Addr, error) { + if addr == "" { + return nil, nil + } switch afnet { case "tcp", "tcp4", "tcp6": - if addr != "" { - a, err = ResolveTCPAddr(afnet, addr) - } + return resolveTCPAddr(afnet, addr, deadline) case "udp", "udp4", "udp6": - if addr != "" { - a, err = ResolveUDPAddr(afnet, addr) - } + return resolveUDPAddr(afnet, addr, deadline) case "ip", "ip4", "ip6": - if addr != "" { - a, err = ResolveIPAddr(afnet, addr) - } + return resolveIPAddr(afnet, addr, deadline) case "unix", "unixgram", "unixpacket": - if addr != "" { - a, err = ResolveUnixAddr(afnet, addr) - } + return ResolveUnixAddr(afnet, addr) } - return + return nil, nil } // Dial connects to the address addr on the network net. @@ -89,23 +90,23 @@ func resolveNetAddr(op, net, addr string) (afnet string, a Addr, err error) { // Dial("ip6:ospf", "::1") // func Dial(net, addr string) (Conn, error) { - _, addri, err := resolveNetAddr("dial", net, addr) + _, addri, err := resolveNetAddr("dial", net, addr, noDeadline) if err != nil { return nil, err } - return dialAddr(net, addr, addri) + return dialAddr(net, addr, addri, noDeadline) } -func dialAddr(net, addr string, addri Addr) (c Conn, err error) { +func dialAddr(net, addr string, addri Addr, deadline time.Time) (c Conn, err error) { switch ra := addri.(type) { case *TCPAddr: - c, err = DialTCP(net, nil, ra) + c, err = dialTCP(net, nil, ra, deadline) case *UDPAddr: - c, err = DialUDP(net, nil, ra) + c, err = dialUDP(net, nil, ra, deadline) case *IPAddr: - c, err = DialIP(net, nil, ra) + c, err = dialIP(net, nil, ra, deadline) case *UnixAddr: - c, err = DialUnix(net, nil, ra) + c, err = dialUnix(net, nil, ra, deadline) default: err = &OpError{"dial", net + " " + addr, nil, UnknownNetworkError(net)} } @@ -115,13 +116,31 @@ func dialAddr(net, addr string, addri Addr) (c Conn, err error) { return } +const useDialTimeoutRace = runtime.GOOS == "windows" || runtime.GOOS == "plan9" + // DialTimeout acts like Dial but takes a timeout. // The timeout includes name resolution, if required. func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) { - // TODO(bradfitz): the timeout should be pushed down into the - // net package's event loop, so on timeout to dead hosts we - // don't have a goroutine sticking around for the default of - // ~3 minutes. + if useDialTimeoutRace { + // On windows and plan9, use the relatively inefficient + // goroutine-racing implementation of DialTimeout that + // doesn't push down deadlines to the pollster. + // TODO: remove this once those are implemented. + return dialTimeoutRace(net, addr, timeout) + } + deadline := time.Now().Add(timeout) + _, addri, err := resolveNetAddr("dial", net, addr, deadline) + if err != nil { + return nil, err + } + return dialAddr(net, addr, addri, deadline) +} + +// dialTimeoutRace is the old implementation of DialTimeout, still used +// on operating systems where the deadline hasn't been pushed down +// into the pollserver. +// TODO: fix this on Windows and plan9. +func dialTimeoutRace(net, addr string, timeout time.Duration) (Conn, error) { t := time.NewTimer(timeout) defer t.Stop() type pair struct { @@ -131,13 +150,13 @@ func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) { ch := make(chan pair, 1) resolvedAddr := make(chan Addr, 1) go func() { - _, addri, err := resolveNetAddr("dial", net, addr) + _, addri, err := resolveNetAddr("dial", net, addr, noDeadline) if err != nil { ch <- pair{nil, err} return } resolvedAddr <- addri // in case we need it for OpError - c, err := dialAddr(net, addr, addri) + c, err := dialAddr(net, addr, addri, noDeadline) ch <- pair{c, err} }() select { @@ -175,7 +194,7 @@ func (a stringAddr) String() string { return a.addr } // The network string net must be a stream-oriented network: // "tcp", "tcp4", "tcp6", "unix" or "unixpacket". func Listen(net, laddr string) (Listener, error) { - afnet, a, err := resolveNetAddr("listen", net, laddr) + afnet, a, err := resolveNetAddr("listen", net, laddr, noDeadline) if err != nil { return nil, err } @@ -200,7 +219,7 @@ func Listen(net, laddr string) (Listener, error) { // The network string net must be a packet-oriented network: // "udp", "udp4", "udp6", "ip", "ip4", "ip6" or "unixgram". func ListenPacket(net, addr string) (PacketConn, error) { - afnet, a, err := resolveNetAddr("listen", net, addr) + afnet, a, err := resolveNetAddr("listen", net, addr, noDeadline) if err != nil { return nil, err } diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index abd6b7f..865dd62 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -7,6 +7,8 @@ package net import ( "flag" "fmt" + "io" + "os" "regexp" "runtime" "testing" @@ -55,7 +57,7 @@ func TestDialTimeout(t *testing.T) { // on our 386 builder, this Dial succeeds, connecting // to an IIS web server somewhere. The data center // or VM or firewall must be stealing the TCP connection. - // + // // IANA Service Name and Transport Protocol Port Number Registry // go func() { @@ -222,3 +224,79 @@ func TestDialError(t *testing.T) { } } } + +func TestDialTimeoutFDLeak(t *testing.T) { + if runtime.GOOS != "linux" { + // TODO(bradfitz): test on other platforms + t.Logf("skipping test on %s", runtime.GOOS) + return + } + + ln := newLocalListener(t) + defer ln.Close() + + type connErr struct { + conn Conn + err error + } + dials := listenerBacklog + 100 + maxGoodConnect := listenerBacklog + 5 // empirically 131 good ones (of 128). who knows? + resc := make(chan connErr) + for i := 0; i < dials; i++ { + go func() { + conn, err := DialTimeout("tcp", ln.Addr().String(), 500*time.Millisecond) + resc <- connErr{conn, err} + }() + } + + var firstErr string + var ngood int + var toClose []io.Closer + for i := 0; i < dials; i++ { + ce := <-resc + if ce.err == nil { + ngood++ + if ngood > maxGoodConnect { + t.Errorf("%d good connects; expected at most %d", ngood, maxGoodConnect) + } + toClose = append(toClose, ce.conn) + continue + } + err := ce.err + if firstErr == "" { + firstErr = err.Error() + } else if err.Error() != firstErr { + t.Fatalf("inconsistent error messages: first was %q, then later %q", firstErr, err) + } + } + for _, c := range toClose { + c.Close() + } + for i := 0; i < 100; i++ { + if got := numFD(); got < dials { + // Test passes. + return + } + time.Sleep(10 * time.Millisecond) + } + if got := numFD(); got >= dials { + t.Errorf("num fds after %d timeouts = %d; want <%d", dials, got, dials) + } +} + +func numFD() int { + if runtime.GOOS == "linux" { + f, err := os.Open("/proc/self/fd") + if err != nil { + panic(err) + } + defer f.Close() + names, err := f.Readdirnames(0) + if err != nil { + panic(err) + } + return len(names) + } + // All tests using this should be skipped anyway, but: + panic("numFDs not implemented on " + runtime.GOOS) +} diff --git a/libgo/go/net/dialgoogle_test.go b/libgo/go/net/dialgoogle_test.go index 426e2ff..dd3c4ba 100644 --- a/libgo/go/net/dialgoogle_test.go +++ b/libgo/go/net/dialgoogle_test.go @@ -116,7 +116,12 @@ func TestDialGoogleIPv6(t *testing.T) { return } // Only run tcp6 if the kernel will take it. - if !*testIPv6 || !supportsIPv6 { + if !supportsIPv6 { + t.Logf("skipping test; ipv6 is not supported") + return + } + if !*testIPv6 { + t.Logf("test disabled; use -ipv6 to enable") return } diff --git a/libgo/go/net/dnsclient.go b/libgo/go/net/dnsclient.go index e69cb31..76b1926 100644 --- a/libgo/go/net/dnsclient.go +++ b/libgo/go/net/dnsclient.go @@ -183,7 +183,7 @@ func (s byPriorityWeight) Less(i, j int) bool { } // shuffleByWeight shuffles SRV records by weight using the algorithm -// described in RFC 2782. +// described in RFC 2782. func (addrs byPriorityWeight) shuffleByWeight() { sum := 0 for _, addr := range addrs { @@ -244,3 +244,8 @@ func (s byPref) sort() { } sort.Sort(s) } + +// An NS represents a single DNS NS record. +type NS struct { + Host string +} diff --git a/libgo/go/net/dnsmsg.go b/libgo/go/net/dnsmsg.go index b6ebe11..161afb2 100644 --- a/libgo/go/net/dnsmsg.go +++ b/libgo/go/net/dnsmsg.go @@ -618,7 +618,7 @@ func printStruct(any dnsStruct) string { s += name + "=" switch tag { case "ipv4": - i := val.(uint32) + i := *val.(*uint32) s += IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i)).String() case "ipv6": i := val.([]byte) diff --git a/libgo/go/net/fd_plan9.go b/libgo/go/net/fd_plan9.go new file mode 100644 index 0000000..6d7ab38 --- /dev/null +++ b/libgo/go/net/fd_plan9.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. + +package net + +import ( + "io" + "os" + "syscall" + "time" +) + +// Network file descritor. +type netFD struct { + proto, name, dir string + ctl, data *os.File + laddr, raddr Addr +} + +var canCancelIO = true // used for testing current package + +func sysInit() { +} + +func newFD(proto, name string, ctl *os.File, laddr, raddr Addr) *netFD { + return &netFD{proto, name, "/net/" + proto + "/" + name, ctl, nil, laddr, raddr} +} + +func (fd *netFD) ok() bool { return fd != nil && fd.ctl != nil } + +func (fd *netFD) Read(b []byte) (n int, err error) { + if !fd.ok() { + return 0, syscall.EINVAL + } + if fd.data == nil { + fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0) + if err != nil { + return 0, err + } + } + n, err = fd.data.Read(b) + if fd.proto == "udp" && err == io.EOF { + n = 0 + err = nil + } + return +} + +func (fd *netFD) Write(b []byte) (n int, err error) { + if !fd.ok() { + return 0, syscall.EINVAL + } + if fd.data == nil { + fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0) + if err != nil { + return 0, err + } + } + return fd.data.Write(b) +} + +func (fd *netFD) CloseRead() error { + if !fd.ok() { + return syscall.EINVAL + } + return syscall.EPLAN9 +} + +func (fd *netFD) CloseWrite() error { + if !fd.ok() { + return syscall.EINVAL + } + return syscall.EPLAN9 +} + +func (fd *netFD) Close() error { + if !fd.ok() { + return syscall.EINVAL + } + err := fd.ctl.Close() + if err != nil { + return err + } + if fd.data != nil { + err = fd.data.Close() + } + fd.ctl = nil + fd.data = nil + return err +} + +func (fd *netFD) dup() (*os.File, error) { + return nil, syscall.EPLAN9 +} + +func setDeadline(fd *netFD, t time.Time) error { + return syscall.EPLAN9 +} + +func setReadDeadline(fd *netFD, t time.Time) error { + return syscall.EPLAN9 +} + +func setWriteDeadline(fd *netFD, t time.Time) error { + return syscall.EPLAN9 +} + +func setReadBuffer(fd *netFD, bytes int) error { + return syscall.EPLAN9 +} + +func setWriteBuffer(fd *netFD, bytes int) error { + return syscall.EPLAN9 +} diff --git a/libgo/go/net/fd_unix.go b/libgo/go/net/fd_unix.go index c55f336..096ad41 100644 --- a/libgo/go/net/fd_unix.go +++ b/libgo/go/net/fd_unix.go @@ -7,7 +7,6 @@ package net import ( - "errors" "io" "os" "runtime" @@ -97,15 +96,11 @@ func (s *pollServer) AddFD(fd *netFD, mode int) error { } wake, err := s.poll.AddFD(intfd, mode, false) + s.Unlock() if err != nil { - panic("pollServer AddFD " + err.Error()) - } - if wake { - doWakeup = true + return &OpError{"addfd", fd.net, fd.laddr, err} } - s.Unlock() - - if doWakeup { + if wake || doWakeup { s.Wakeup() } return nil @@ -167,7 +162,7 @@ func (s *pollServer) CheckDeadlines() { // TODO(rsc): This will need to be handled more efficiently, // probably with a heap indexed by wakeup time. - var next_deadline int64 + var nextDeadline int64 for key, fd := range s.pending { var t int64 var mode int @@ -192,12 +187,12 @@ func (s *pollServer) CheckDeadlines() { fd.wdeadline = -1 } s.WakeFD(fd, mode, nil) - } else if next_deadline == 0 || t < next_deadline { - next_deadline = t + } else if nextDeadline == 0 || t < nextDeadline { + nextDeadline = t } } } - s.deadline = next_deadline + s.deadline = nextDeadline } func (s *pollServer) Run() { @@ -265,7 +260,9 @@ var pollMaxN int var pollservers []*pollServer var startServersOnce []func() -func init() { +var canCancelIO = true // used for testing current package + +func sysInit() { pollMaxN = runtime.NumCPU() if pollMaxN > 8 { pollMaxN = 8 // No improvement then. @@ -316,22 +313,30 @@ func newFD(fd, family, sotype int, net string) (*netFD, error) { func (fd *netFD) setAddr(laddr, raddr Addr) { fd.laddr = laddr fd.raddr = raddr + fd.sysfile = os.NewFile(uintptr(fd.sysfd), fd.net) +} + +func (fd *netFD) name() string { var ls, rs string - if laddr != nil { - ls = laddr.String() + if fd.laddr != nil { + ls = fd.laddr.String() } - if raddr != nil { - rs = raddr.String() + if fd.raddr != nil { + rs = fd.raddr.String() } - fd.sysfile = os.NewFile(uintptr(fd.sysfd), fd.net+":"+ls+"->"+rs) + return fd.net + ":" + ls + "->" + rs } func (fd *netFD) connect(ra syscall.Sockaddr) error { err := syscall.Connect(fd.sysfd, ra) + hadTimeout := fd.wdeadline > 0 if err == syscall.EINPROGRESS { if err = fd.pollServer.WaitWrite(fd); err != nil { return err } + if hadTimeout && fd.wdeadline < 0 { + return errTimeout + } var e int e, err = syscall.GetsockoptInt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR) if err != nil { @@ -344,15 +349,10 @@ func (fd *netFD) connect(ra syscall.Sockaddr) error { return err } -var errClosing = errors.New("use of closed network connection") - // Add a reference to this fd. // If closing==true, pollserver must be locked; mark the fd as closing. // Returns an error if the fd cannot be used. func (fd *netFD) incref(closing bool) error { - if fd == nil { - return errClosing - } fd.sysmu.Lock() if fd.closing { fd.sysmu.Unlock() @@ -369,9 +369,6 @@ func (fd *netFD) incref(closing bool) error { // Remove a reference to this FD and close if we've been asked to do so (and // there are no references left. func (fd *netFD) decref() { - if fd == nil { - return - } fd.sysmu.Lock() fd.sysref-- if fd.closing && fd.sysref == 0 && fd.sysfile != nil { @@ -664,7 +661,7 @@ func (fd *netFD) dup() (f *os.File, err error) { return nil, &OpError{"setnonblock", fd.net, fd.laddr, err} } - return os.NewFile(uintptr(ns), fd.sysfile.Name()), nil + return os.NewFile(uintptr(ns), fd.name()), nil } func closesocket(s int) error { diff --git a/libgo/go/net/fd_unix_test.go b/libgo/go/net/fd_unix_test.go new file mode 100644 index 0000000..d1eb573 --- /dev/null +++ b/libgo/go/net/fd_unix_test.go @@ -0,0 +1,60 @@ +// Copyright 2012 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. + +// +build darwin freebsd linux netbsd openbsd + +package net + +import ( + "testing" +) + +// Issue 3590. netFd.AddFD should return an error +// from the underlying pollster rather than panicing. +func TestAddFDReturnsError(t *testing.T) { + l, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + go func() { + for { + c, err := l.Accept() + if err != nil { + return + } + defer c.Close() + } + }() + + c, err := Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + // replace c's pollServer with a closed version. + ps, err := newPollServer() + if err != nil { + t.Fatal(err) + } + ps.poll.Close() + c.(*TCPConn).conn.fd.pollServer = ps + + var b [1]byte + _, err = c.Read(b[:]) + if err, ok := err.(*OpError); ok { + if err.Op == "addfd" { + return + } + if err, ok := err.Err.(*OpError); ok { + // the err is sometimes wrapped by another OpError + if err.Op == "addfd" { + return + } + } + } + t.Error(err) +} diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go index 4ae7839..5338def 100644 --- a/libgo/go/net/fd_windows.go +++ b/libgo/go/net/fd_windows.go @@ -17,19 +17,35 @@ import ( var initErr error -func init() { +// CancelIo Windows API cancels all outstanding IO for a particular +// socket on current thread. To overcome that limitation, we run +// special goroutine, locked to OS single thread, that both starts +// and cancels IO. It means, there are 2 unavoidable thread switches +// for every IO. +// Some newer versions of Windows has new CancelIoEx API, that does +// not have that limitation and can be used from any thread. This +// package uses CancelIoEx API, if present, otherwise it fallback +// to CancelIo. + +var canCancelIO bool // determines if CancelIoEx API is present + +func sysInit() { var d syscall.WSAData e := syscall.WSAStartup(uint32(0x202), &d) if e != nil { initErr = os.NewSyscallError("WSAStartup", e) } + canCancelIO = syscall.LoadCancelIoEx() == nil + if syscall.LoadGetAddrInfo() == nil { + lookupIP = newLookupIP + } } func closesocket(s syscall.Handle) error { return syscall.Closesocket(s) } -// Interface for all io operations. +// Interface for all IO operations. type anOpIface interface { Op() *anOp Name() string @@ -42,7 +58,7 @@ type ioResult struct { err error } -// anOp implements functionality common to all io operations. +// anOp implements functionality common to all IO operations. type anOp struct { // Used by IOCP interface, it must be first field // of the struct, as our code rely on it. @@ -75,7 +91,7 @@ func (o *anOp) Op() *anOp { return o } -// bufOp is used by io operations that read / write +// bufOp is used by IO operations that read / write // data from / to client buffer. type bufOp struct { anOp @@ -92,7 +108,7 @@ func (o *bufOp) Init(fd *netFD, buf []byte, mode int) { } } -// resultSrv will retrieve all io completion results from +// resultSrv will retrieve all IO completion results from // iocp and send them to the correspondent waiting client // goroutine via channel supplied in the request. type resultSrv struct { @@ -107,7 +123,7 @@ func (s *resultSrv) Run() { r.err = syscall.GetQueuedCompletionStatus(s.iocp, &(r.qty), &key, &o, syscall.INFINITE) switch { case r.err == nil: - // Dequeued successfully completed io packet. + // Dequeued successfully completed IO packet. case r.err == syscall.Errno(syscall.WAIT_TIMEOUT) && o == nil: // Wait has timed out (should not happen now, but might be used in the future). panic("GetQueuedCompletionStatus timed out") @@ -115,22 +131,23 @@ func (s *resultSrv) Run() { // Failed to dequeue anything -> report the error. panic("GetQueuedCompletionStatus failed " + r.err.Error()) default: - // Dequeued failed io packet. + // Dequeued failed IO packet. } (*anOp)(unsafe.Pointer(o)).resultc <- r } } -// ioSrv executes net io requests. +// ioSrv executes net IO requests. type ioSrv struct { - submchan chan anOpIface // submit io requests - canchan chan anOpIface // cancel io requests + submchan chan anOpIface // submit IO requests + canchan chan anOpIface // cancel IO requests } -// ProcessRemoteIO will execute submit io requests on behalf +// ProcessRemoteIO will execute submit IO requests on behalf // of other goroutines, all on a single os thread, so it can // cancel them later. Results of all operations will be sent // back to their requesters via channel supplied in request. +// It is used only when the CancelIoEx API is unavailable. func (s *ioSrv) ProcessRemoteIO() { runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -144,20 +161,21 @@ func (s *ioSrv) ProcessRemoteIO() { } } -// ExecIO executes a single io operation. It either executes it -// inline, or, if a deadline is employed, passes the request onto +// ExecIO executes a single IO operation oi. It submits and cancels +// IO in the current thread for systems where Windows CancelIoEx API +// is available. Alternatively, it passes the request onto // a special goroutine and waits for completion or cancels request. // deadline is unix nanos. func (s *ioSrv) ExecIO(oi anOpIface, deadline int64) (int, error) { var err error o := oi.Op() - if deadline != 0 { + if canCancelIO { + err = oi.Submit() + } else { // Send request to a special dedicated thread, - // so it can stop the io with CancelIO later. + // so it can stop the IO with CancelIO later. s.submchan <- oi err = <-o.errnoc - } else { - err = oi.Submit() } switch err { case nil: @@ -168,27 +186,50 @@ func (s *ioSrv) ExecIO(oi anOpIface, deadline int64) (int, error) { default: return 0, &OpError{oi.Name(), o.fd.net, o.fd.laddr, err} } - // Wait for our request to complete. - var r ioResult + // Setup timer, if deadline is given. + var timer <-chan time.Time if deadline != 0 { dt := deadline - time.Now().UnixNano() if dt < 1 { dt = 1 } - timer := time.NewTimer(time.Duration(dt) * time.Nanosecond) - defer timer.Stop() - select { - case r = <-o.resultc: - case <-timer.C: + t := time.NewTimer(time.Duration(dt) * time.Nanosecond) + defer t.Stop() + timer = t.C + } + // Wait for our request to complete. + var r ioResult + var cancelled, timeout bool + select { + case r = <-o.resultc: + case <-timer: + cancelled = true + timeout = true + case <-o.fd.closec: + cancelled = true + } + if cancelled { + // Cancel it. + if canCancelIO { + err := syscall.CancelIoEx(syscall.Handle(o.Op().fd.sysfd), &o.o) + // Assuming ERROR_NOT_FOUND is returned, if IO is completed. + if err != nil && err != syscall.ERROR_NOT_FOUND { + // TODO(brainman): maybe do something else, but panic. + panic(err) + } + } else { s.canchan <- oi <-o.errnoc - r = <-o.resultc - if r.err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled - r.err = syscall.EWOULDBLOCK - } } - } else { + // Wait for IO to be canceled or complete successfully. r = <-o.resultc + if r.err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled + if timeout { + r.err = errTimeout + } else { + r.err = errClosing + } + } } if r.err != nil { err = &OpError{oi.Name(), o.fd.net, o.fd.laddr, r.err} @@ -211,9 +252,13 @@ func startServer() { go resultsrv.Run() iosrv = new(ioSrv) - iosrv.submchan = make(chan anOpIface) - iosrv.canchan = make(chan anOpIface) - go iosrv.ProcessRemoteIO() + if !canCancelIO { + // Only CancelIo API is available. Lets start special goroutine + // locked to an OS thread, that both starts and cancels IO. + iosrv.submchan = make(chan anOpIface) + iosrv.canchan = make(chan anOpIface) + go iosrv.ProcessRemoteIO() + } } // Network file descriptor. @@ -233,6 +278,7 @@ type netFD struct { raddr Addr resultc [2]chan ioResult // read/write completion results errnoc [2]chan error // read/write submit or cancel operation errors + closec chan bool // used by Close to cancel pending IO // owned by client rdeadline int64 @@ -247,6 +293,7 @@ func allocFD(fd syscall.Handle, family, sotype int, net string) *netFD { family: family, sotype: sotype, net: net, + closec: make(chan bool), } runtime.SetFinalizer(netfd, (*netFD).Close) return netfd @@ -273,8 +320,6 @@ func (fd *netFD) connect(ra syscall.Sockaddr) error { return syscall.Connect(fd.sysfd, ra) } -var errClosing = errors.New("use of closed network connection") - // Add a reference to this fd. // If closing==true, mark the fd as closing. // Returns an error if the fd cannot be used. @@ -299,24 +344,12 @@ func (fd *netFD) incref(closing bool) error { // Remove a reference to this FD and close if we've been asked to do so (and // there are no references left. func (fd *netFD) decref() { + if fd == nil { + return + } fd.sysmu.Lock() fd.sysref-- - // NOTE(rsc): On Unix we check fd.sysref == 0 here before closing, - // but on Windows we have no way to wake up the blocked I/O other - // than closing the socket (or calling Shutdown, which breaks other - // programs that might have a reference to the socket). So there is - // a small race here that we might close fd.sysfd and then some other - // goroutine might start a read of fd.sysfd (having read it before we - // write InvalidHandle to it), which might refer to some other file - // if the specific handle value gets reused. I think handle values on - // Windows are not reused as aggressively as file descriptors on Unix, - // so this might be tolerable. - if fd.closing && fd.sysfd != syscall.InvalidHandle { - // 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 resultsrv for Close too. Sigh. - syscall.SetNonblock(fd.sysfd, false) + if fd.closing && fd.sysref == 0 && fd.sysfd != syscall.InvalidHandle { closesocket(fd.sysfd) fd.sysfd = syscall.InvalidHandle // no need for a finalizer anymore @@ -329,14 +362,22 @@ func (fd *netFD) Close() error { if err := fd.incref(true); err != nil { return err } - fd.decref() + defer fd.decref() + // unblock pending reader and writer + close(fd.closec) + // wait for both reader and writer to exit + fd.rio.Lock() + defer fd.rio.Unlock() + fd.wio.Lock() + defer fd.wio.Unlock() return nil } func (fd *netFD) shutdown(how int) error { - if fd == nil || fd.sysfd == syscall.InvalidHandle { - return syscall.EINVAL + if err := fd.incref(false); err != nil { + return err } + defer fd.decref() err := syscall.Shutdown(fd.sysfd, how) if err != nil { return &OpError{"shutdown", fd.net, fd.laddr, err} @@ -368,18 +409,12 @@ func (o *readOp) Name() string { } func (fd *netFD) Read(buf []byte) (int, error) { - if fd == nil { - return 0, syscall.EINVAL - } - fd.rio.Lock() - defer fd.rio.Unlock() if err := fd.incref(false); err != nil { return 0, err } defer fd.decref() - if fd.sysfd == syscall.InvalidHandle { - return 0, syscall.EINVAL - } + fd.rio.Lock() + defer fd.rio.Unlock() var o readOp o.Init(fd, buf, 'r') n, err := iosrv.ExecIO(&o, fd.rdeadline) @@ -407,18 +442,15 @@ func (o *readFromOp) Name() string { } func (fd *netFD) ReadFrom(buf []byte) (n int, sa syscall.Sockaddr, err error) { - if fd == nil { - return 0, nil, syscall.EINVAL - } if len(buf) == 0 { return 0, nil, nil } - fd.rio.Lock() - defer fd.rio.Unlock() if err := fd.incref(false); err != nil { return 0, nil, err } defer fd.decref() + fd.rio.Lock() + defer fd.rio.Unlock() var o readFromOp o.Init(fd, buf, 'r') o.rsan = int32(unsafe.Sizeof(o.rsa)) @@ -446,15 +478,12 @@ func (o *writeOp) Name() string { } func (fd *netFD) Write(buf []byte) (int, error) { - if fd == nil { - return 0, syscall.EINVAL - } - fd.wio.Lock() - defer fd.wio.Unlock() if err := fd.incref(false); err != nil { return 0, err } defer fd.decref() + fd.wio.Lock() + defer fd.wio.Unlock() var o writeOp o.Init(fd, buf, 'w') return iosrv.ExecIO(&o, fd.wdeadline) @@ -477,21 +506,15 @@ func (o *writeToOp) Name() string { } func (fd *netFD) WriteTo(buf []byte, sa syscall.Sockaddr) (int, error) { - if fd == nil { - return 0, syscall.EINVAL - } if len(buf) == 0 { return 0, nil } - fd.wio.Lock() - defer fd.wio.Unlock() if err := fd.incref(false); err != nil { return 0, err } defer fd.decref() - if fd.sysfd == syscall.InvalidHandle { - return 0, syscall.EINVAL - } + fd.wio.Lock() + defer fd.wio.Unlock() var o writeToOp o.Init(fd, buf, 'w') o.sa = sa @@ -544,7 +567,7 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { var o acceptOp o.Init(fd, 'r') o.newsock = s - _, err = iosrv.ExecIO(&o, 0) + _, err = iosrv.ExecIO(&o, fd.rdeadline) if err != nil { closesocket(s) return nil, err diff --git a/libgo/go/net/http/cgi/testdata/test.cgi b/libgo/go/net/http/cgi/testdata/test.cgi index b46b133..dcefe4e 100644 --- a/libgo/go/net/http/cgi/testdata/test.cgi +++ b/libgo/go/net/http/cgi/testdata/test.cgi @@ -8,6 +8,25 @@ use strict; use Cwd; +binmode STDOUT; + +sub on_windows { + return $^O eq 'MSWin32' || $^O eq 'msys'; +} + +# normalize_windows_path normalizes the various Windows Perl path +# formats into Go's format. +sub normalize_windows_path { + my $dir = shift; + return $dir unless on_windows(); + $dir =~ s!^[a-z]:!uc($&)!e; + if ($dir =~ s!^/([a-zA-Z])/!!) { + $dir = uc($1) . ":\\$dir"; + } + $dir =~ s!/!\\!g; + return $dir; +} + my $q = MiniCGI->new; my $params = $q->Vars; @@ -20,7 +39,7 @@ my $NL = "\r\n"; $NL = "\n" if $params->{mode} eq "NL"; my $p = sub { - print "$_[0]$NL"; + print "$_[0]$NL"; }; # With carriage returns @@ -39,29 +58,18 @@ if ($params->{"bigresponse"}) { print "test=Hello CGI\n"; foreach my $k (sort keys %$params) { - print "param-$k=$params->{$k}\n"; + print "param-$k=$params->{$k}\n"; } foreach my $k (sort keys %ENV) { - my $clean_env = $ENV{$k}; - $clean_env =~ s/[\n\r]//g; - print "env-$k=$clean_env\n"; + my $clean_env = $ENV{$k}; + $clean_env =~ s/[\n\r]//g; + print "env-$k=$clean_env\n"; } -# NOTE: don't call getcwd() for windows. -# msys return /c/go/src/... not C:\go\... -my $dir; -if ($^O eq 'MSWin32' || $^O eq 'msys') { - my $cmd = $ENV{'COMSPEC'} || 'c:\\windows\\system32\\cmd.exe'; - $cmd =~ s!\\!/!g; - $dir = `$cmd /c cd`; - chomp $dir; -} else { - $dir = getcwd(); -} +my $dir = normalize_windows_path(getcwd()); print "cwd=$dir\n"; - # A minimal version of CGI.pm, for people without the perl-modules # package installed. (CGI.pm used to be part of the Perl core, but # some distros now bundle perl-base and perl-modules separately...) @@ -94,3 +102,24 @@ sub _urldecode { $v =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; return $v; } + +package Tests; + +sub test_normalize_windows_paths { + my @tests = ( + {in => "C:\\foo\\bar", want => "C:\\foo\\bar"}, + {in => "C:/foo/bar", want => "C:\\foo\\bar"}, + {in => "c:/foo/bar", want => "C:\\foo\\bar"}, + {in => "/c/foo/bar", want => "C:\\foo\\bar"}, + ); + foreach my $tt (@tests) { + my $got = ::normalize_windows_path($tt->{in}); + unless ($got eq $tt->{want}) { + die "For path $tt->{in}, normalize = $got; want $tt->{want}\n"; + } + } +} + +BEGIN { + test_normalize_windows_paths() if ::on_windows(); +} diff --git a/libgo/go/net/http/chunked.go b/libgo/go/net/http/chunked.go index 60a478f..7cf39cf 100644 --- a/libgo/go/net/http/chunked.go +++ b/libgo/go/net/http/chunked.go @@ -13,6 +13,7 @@ import ( "bufio" "bytes" "errors" + "fmt" "io" "strconv" ) @@ -22,7 +23,7 @@ const maxLineLength = 4096 // assumed <= bufio.defaultBufSize var ErrLineTooLong = errors.New("header line too long") // newChunkedReader returns a new chunkedReader that translates the data read from r -// out of HTTP "chunked" format before returning it. +// out of HTTP "chunked" format before returning it. // The chunkedReader returns io.EOF when the final 0-length chunk is read. // // newChunkedReader is not needed by normal applications. The http package @@ -39,6 +40,7 @@ type chunkedReader struct { r *bufio.Reader n uint64 // unread bytes in chunk err error + buf [2]byte } func (cr *chunkedReader) beginChunk() { @@ -74,9 +76,8 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err error) { cr.n -= uint64(n) if cr.n == 0 && cr.err == nil { // end of chunk (CRLF) - b := make([]byte, 2) - if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { - if b[0] != '\r' || b[1] != '\n' { + if _, cr.err = io.ReadFull(cr.r, cr.buf[:]); cr.err == nil { + if cr.buf[0] != '\r' || cr.buf[1] != '\n' { cr.err = errors.New("malformed chunked encoding") } } @@ -147,9 +148,7 @@ func (cw *chunkedWriter) Write(data []byte) (n int, err error) { return 0, nil } - head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" - - if _, err = io.WriteString(cw.Wire, head); err != nil { + if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { return 0, err } if n, err = cw.Wire.Write(data); err != nil { diff --git a/libgo/go/net/http/client.go b/libgo/go/net/http/client.go index 02891db..2f957d2 100644 --- a/libgo/go/net/http/client.go +++ b/libgo/go/net/http/client.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // HTTP client. See RFC 2616. -// +// // This is the high-level Client interface. // The low-level implementation is in transport.go. @@ -44,8 +44,8 @@ type Client struct { // which is to stop after 10 consecutive requests. CheckRedirect func(req *Request, via []*Request) error - // Jar specifies the cookie jar. - // If Jar is nil, cookies are not sent in requests and ignored + // Jar specifies the cookie jar. + // If Jar is nil, cookies are not sent in requests and ignored // in responses. Jar CookieJar } @@ -87,6 +87,22 @@ type readClose struct { io.Closer } +func (c *Client) send(req *Request) (*Response, error) { + if c.Jar != nil { + for _, cookie := range c.Jar.Cookies(req.URL) { + req.AddCookie(cookie) + } + } + resp, err := send(req, c.Transport) + if err != nil { + return nil, err + } + if c.Jar != nil { + c.Jar.SetCookies(req.URL, resp.Cookies()) + } + return resp, err +} + // Do sends an HTTP request and returns an HTTP response, following // policy (e.g. redirects, cookies, auth) as configured on the client. // @@ -106,7 +122,7 @@ func (c *Client) Do(req *Request) (resp *Response, err error) { if req.Method == "GET" || req.Method == "HEAD" { return c.doFollowingRedirects(req) } - return send(req, c.Transport) + return c.send(req) } // send issues an HTTP request. @@ -215,11 +231,6 @@ func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error) return nil, errors.New("http: nil Request.URL") } - jar := c.Jar - if jar == nil { - jar = blackHoleJar{} - } - req := ireq urlStr := "" // next relative or absolute URL to fetch (after first request) redirectFailed := false @@ -247,16 +258,10 @@ func (c *Client) doFollowingRedirects(ireq *Request) (resp *Response, err error) } } - for _, cookie := range jar.Cookies(req.URL) { - req.AddCookie(cookie) - } urlStr = req.URL.String() - if resp, err = send(req, c.Transport); err != nil { + if resp, err = c.send(req); err != nil { break } - if c := resp.Cookies(); len(c) > 0 { - jar.SetCookies(req.URL, c) - } if shouldRedirect(resp.StatusCode) { resp.Body.Close() @@ -316,16 +321,7 @@ func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Respon return nil, err } req.Header.Set("Content-Type", bodyType) - if c.Jar != nil { - for _, cookie := range c.Jar.Cookies(req.URL) { - req.AddCookie(cookie) - } - } - resp, err = send(req, c.Transport) - if err == nil && c.Jar != nil { - c.Jar.SetCookies(req.URL, resp.Cookies()) - } - return + return c.send(req) } // PostForm issues a POST to the specified URL, with data's keys and @@ -339,7 +335,7 @@ func PostForm(url string, data url.Values) (resp *Response, err error) { return DefaultClient.PostForm(url, data) } -// PostForm issues a POST to the specified URL, +// PostForm issues a POST to the specified URL, // with data's keys and values urlencoded as the request body. // // When err is nil, resp always contains a non-nil resp.Body. diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index c61b17d..9a45b14 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -219,6 +219,9 @@ func TestRedirects(t *testing.T) { return checkErr }} res, err := c.Get(ts.URL) + if err != nil { + t.Fatalf("Get error: %v", err) + } finalUrl := res.Request.URL.String() if e, g := "", fmt.Sprintf("%v", err); e != g { t.Errorf("with custom client, expected error %q, got %q", e, g) @@ -285,6 +288,10 @@ func TestClientSendsCookieFromJar(t *testing.T) { req, _ := NewRequest("GET", us, nil) client.Do(req) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) + + req, _ = NewRequest("POST", us, nil) + client.Do(req) // Note: doesn't hit network + matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) } // Just enough correctness for our redirect tests. Uses the URL.Host as the @@ -331,7 +338,10 @@ func TestRedirectCookiesJar(t *testing.T) { c.Jar = &TestJar{perURL: make(map[string][]*Cookie)} u, _ := url.Parse(ts.URL) c.Jar.SetCookies(u, []*Cookie{expectedCookies[0]}) - resp, _ := c.Get(ts.URL) + resp, err := c.Get(ts.URL) + if err != nil { + t.Fatalf("Get: %v", err) + } matchReturnedCookies(t, expectedCookies, resp.Cookies()) } diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index 43f519d..155b092 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -26,7 +26,7 @@ type Cookie struct { Expires time.Time RawExpires string - // MaxAge=0 means no 'Max-Age' attribute specified. + // MaxAge=0 means no 'Max-Age' attribute specified. // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' // MaxAge>0 means Max-Age attribute present and given in seconds MaxAge int diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index 1e9186a..f84f739 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -217,7 +217,7 @@ var readCookiesTests = []struct { func TestReadCookies(t *testing.T) { for i, tt := range readCookiesTests { - for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input + for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input c := readCookies(tt.Header, tt.Filter) if !reflect.DeepEqual(c, tt.Cookies) { t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies)) diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go index 09d5cfa..59e30a1 100644 --- a/libgo/go/net/http/fs_test.go +++ b/libgo/go/net/http/fs_test.go @@ -648,6 +648,8 @@ func TestServeContent(t *testing.T) { if err != nil { t.Fatal(err) } + io.Copy(ioutil.Discard, res.Body) + res.Body.Close() if res.StatusCode != tt.wantStatus { t.Errorf("test %q: status = %d; want %d", testName, res.StatusCode, tt.wantStatus) } diff --git a/libgo/go/net/http/httputil/chunked.go b/libgo/go/net/http/httputil/chunked.go index 29eaf34..26daee5 100644 --- a/libgo/go/net/http/httputil/chunked.go +++ b/libgo/go/net/http/httputil/chunked.go @@ -15,6 +15,7 @@ import ( "bufio" "bytes" "errors" + "fmt" "io" "strconv" ) @@ -24,7 +25,7 @@ const maxLineLength = 4096 // assumed <= bufio.defaultBufSize var ErrLineTooLong = errors.New("header line too long") // NewChunkedReader returns a new chunkedReader that translates the data read from r -// out of HTTP "chunked" format before returning it. +// out of HTTP "chunked" format before returning it. // The chunkedReader returns io.EOF when the final 0-length chunk is read. // // NewChunkedReader is not needed by normal applications. The http package @@ -41,6 +42,7 @@ type chunkedReader struct { r *bufio.Reader n uint64 // unread bytes in chunk err error + buf [2]byte } func (cr *chunkedReader) beginChunk() { @@ -76,9 +78,8 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err error) { cr.n -= uint64(n) if cr.n == 0 && cr.err == nil { // end of chunk (CRLF) - b := make([]byte, 2) - if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { - if b[0] != '\r' || b[1] != '\n' { + if _, cr.err = io.ReadFull(cr.r, cr.buf[:]); cr.err == nil { + if cr.buf[0] != '\r' || cr.buf[1] != '\n' { cr.err = errors.New("malformed chunked encoding") } } @@ -149,9 +150,7 @@ func (cw *chunkedWriter) Write(data []byte) (n int, err error) { return 0, nil } - head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" - - if _, err = io.WriteString(cw.Wire, head); err != nil { + if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { return 0, err } if n, err = cw.Wire.Write(data); err != nil { diff --git a/libgo/go/net/http/jar.go b/libgo/go/net/http/jar.go index 2c2caa2..35eee68 100644 --- a/libgo/go/net/http/jar.go +++ b/libgo/go/net/http/jar.go @@ -8,23 +8,18 @@ import ( "net/url" ) -// A CookieJar manages storage and use of cookies in HTTP requests. +// A CookieJar manages storage and use of cookies in HTTP requests. // // Implementations of CookieJar must be safe for concurrent use by multiple // goroutines. type CookieJar interface { - // SetCookies handles the receipt of the cookies in a reply for the - // given URL. It may or may not choose to save the cookies, depending - // on the jar's policy and implementation. + // SetCookies handles the receipt of the cookies in a reply for the + // given URL. It may or may not choose to save the cookies, depending + // on the jar's policy and implementation. SetCookies(u *url.URL, cookies []*Cookie) // Cookies returns the cookies to send in a request for the given URL. - // It is up to the implementation to honor the standard cookie use - // restrictions such as in RFC 6265. + // It is up to the implementation to honor the standard cookie use + // restrictions such as in RFC 6265. Cookies(u *url.URL) []*Cookie } - -type blackHoleJar struct{} - -func (blackHoleJar) SetCookies(u *url.URL, cookies []*Cookie) {} -func (blackHoleJar) Cookies(u *url.URL) []*Cookie { return nil } diff --git a/libgo/go/net/http/proxy_test.go b/libgo/go/net/http/proxy_test.go index 5ecffaf..86db976 100644 --- a/libgo/go/net/http/proxy_test.go +++ b/libgo/go/net/http/proxy_test.go @@ -25,7 +25,7 @@ var UseProxyTests = []struct { {"[::2]", true}, // not a loopback address {"barbaz.net", false}, // match as .barbaz.net - {"foobar.com", false}, // have a port but match + {"foobar.com", false}, // have a port but match {"foofoobar.com", true}, // not match as a part of foobar.com {"baz.com", true}, // not match as a part of barbaz.com {"localhost.net", true}, // not match as suffix of address diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index db7419b..c0b738c 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -161,7 +161,7 @@ func TestSetBasicAuth(t *testing.T) { } func TestMultipartRequest(t *testing.T) { - // Test that we can read the values and files of a + // Test that we can read the values and files of a // multipart request with FormValue and FormFile, // and that ParseMultipartForm can be called multiple times. req := newTestMultipartRequest(t) diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index 71b7b3f..355efb2 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -314,7 +314,7 @@ func TestServerTimeouts(t *testing.T) { l.Close() } -// TestIdentityResponse verifies that a handler can unset +// TestIdentityResponse verifies that a handler can unset func TestIdentityResponse(t *testing.T) { handler := HandlerFunc(func(rw ResponseWriter, req *Request) { rw.Header().Set("Content-Length", "3") @@ -1063,7 +1063,7 @@ type countReader struct { func (cr countReader) Read(p []byte) (n int, err error) { n, err = cr.r.Read(p) - *cr.n += int64(n) + atomic.AddInt64(cr.n, int64(n)) return } @@ -1081,8 +1081,8 @@ func TestRequestBodyLimit(t *testing.T) { })) defer ts.Close() - nWritten := int64(0) - req, _ := NewRequest("POST", ts.URL, io.LimitReader(countReader{neverEnding('a'), &nWritten}, limit*200)) + nWritten := new(int64) + req, _ := NewRequest("POST", ts.URL, io.LimitReader(countReader{neverEnding('a'), nWritten}, limit*200)) // Send the POST, but don't care it succeeds or not. The // remote side is going to reply and then close the TCP @@ -1095,7 +1095,7 @@ func TestRequestBodyLimit(t *testing.T) { // the remote side hung up on us before we wrote too much. _, _ = DefaultClient.Do(req) - if nWritten > limit*100 { + if atomic.LoadInt64(nWritten) > limit*100 { t.Errorf("handler restricted the request body to %d bytes, but client managed to write %d", limit, nWritten) } diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index ee57e01..719cecf 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -579,13 +579,27 @@ func (c *conn) close() { } } -// closeWrite flushes any outstanding data and sends a FIN packet (if client -// is connected via TCP), signalling that we're done. -func (c *conn) closeWrite() { +// rstAvoidanceDelay is the amount of time we sleep after closing the +// write side of a TCP connection before closing the entire socket. +// By sleeping, we increase the chances that the client sees our FIN +// and processes its final data before they process the subsequent RST +// from closing a connection with known unread data. +// This RST seems to occur mostly on BSD systems. (And Windows?) +// This timeout is somewhat arbitrary (~latency around the planet). +const rstAvoidanceDelay = 500 * time.Millisecond + +// closeWrite flushes any outstanding data and sends a FIN packet (if +// client is connected via TCP), signalling that we're done. We then +// pause for a bit, hoping the client processes it before `any +// subsequent RST. +// +// See http://golang.org/issue/3595 +func (c *conn) closeWriteAndWait() { c.finalFlush() if tcp, ok := c.rwc.(*net.TCPConn); ok { tcp.CloseWrite() } + time.Sleep(rstAvoidanceDelay) } // Serve a new connection. @@ -618,20 +632,21 @@ func (c *conn) serve() { for { w, err := c.readRequest() if err != nil { - msg := "400 Bad Request" if err == errTooLarge { // Their HTTP client may or may not be // able to read this if we're // responding to them and hanging up // while they're still writing their // request. Undefined behavior. - msg = "413 Request Entity Too Large" + io.WriteString(c.rwc, "HTTP/1.1 413 Request Entity Too Large\r\n\r\n") + c.closeWriteAndWait() + break } else if err == io.EOF { break // Don't reply } else if neterr, ok := err.(net.Error); ok && neterr.Timeout() { break // Don't reply } - fmt.Fprintf(c.rwc, "HTTP/1.1 %s\r\n\r\n", msg) + io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\n\r\n") break } @@ -685,18 +700,7 @@ func (c *conn) serve() { w.finishRequest() if w.closeAfterReply { if w.requestBodyLimitHit { - // Flush our response and send a FIN packet and wait a bit - // before closing the connection, so the client has a chance - // to read our response before they possibly get a RST from - // our TCP stack from ignoring their unread body. - // See http://golang.org/issue/3595 - c.closeWrite() - // Now wait a bit for our machine to send the FIN and the client's - // machine's HTTP client to read the request before we close - // the connection, which might send a RST (on BSDs, at least). - // 250ms is somewhat arbitrary (~latency around half the planet), - // but this doesn't need to be a full second probably. - time.Sleep(250 * time.Millisecond) + c.closeWriteAndWait() } break } diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index 1fc1e63..9833ddd 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -567,14 +567,22 @@ func seeUpcomingDoubleCRLF(r *bufio.Reader) bool { return false } +var errTrailerEOF = errors.New("http: unexpected EOF reading trailer") + func (b *body) readTrailer() error { // The common case, since nobody uses trailers. - buf, _ := b.r.Peek(2) + buf, err := b.r.Peek(2) if bytes.Equal(buf, singleCRLF) { b.r.ReadByte() b.r.ReadByte() return nil } + if len(buf) < 2 { + return errTrailerEOF + } + if err != nil { + return err + } // Make sure there's a header terminator coming up, to prevent // a DoS with an unbounded size Trailer. It's not easy to @@ -590,6 +598,9 @@ func (b *body) readTrailer() error { hdr, err := textproto.NewReader(b.r).ReadMIMEHeader() if err != nil { + if err == io.EOF { + return errTrailerEOF + } return err } switch rr := b.hdr.(type) { diff --git a/libgo/go/net/http/transfer_test.go b/libgo/go/net/http/transfer_test.go new file mode 100644 index 0000000..8627a37 --- /dev/null +++ b/libgo/go/net/http/transfer_test.go @@ -0,0 +1,37 @@ +// Copyright 2012 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 http + +import ( + "bufio" + "strings" + "testing" +) + +func TestBodyReadBadTrailer(t *testing.T) { + b := &body{ + Reader: strings.NewReader("foobar"), + hdr: true, // force reading the trailer + r: bufio.NewReader(strings.NewReader("")), + } + buf := make([]byte, 7) + n, err := b.Read(buf[:3]) + got := string(buf[:n]) + if got != "foo" || err != nil { + t.Fatalf(`first Read = %d (%q), %v; want 3 ("foo")`, n, got, err) + } + + n, err = b.Read(buf[:]) + got = string(buf[:n]) + if got != "bar" || err != nil { + t.Fatalf(`second Read = %d (%q), %v; want 3 ("bar")`, n, got, err) + } + + n, err = b.Read(buf[:]) + got = string(buf[:n]) + if err == nil { + t.Errorf("final Read was successful (%q), expected error from trailer read", got) + } +} diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index 651f3ce..38ea6f7 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // HTTP client implementation. See RFC 2616. -// +// // This is the low-level Transport implementation of RoundTripper. // The high-level interface is in client.go. @@ -24,6 +24,7 @@ import ( "os" "strings" "sync" + "sync/atomic" "time" ) @@ -604,6 +605,9 @@ func (pc *persistConn) readLoop() { alive = false } + // TODO(bradfitz): this hasBody conflicts with the defition + // above which excludes HEAD requests. Is this one + // incomplete? hasBody := resp != nil && resp.ContentLength != 0 var waitForBodyRead chan bool if hasBody { @@ -706,7 +710,7 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err // requested it. requestedGzip := false if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" { - // Request gzip only, not deflate. Deflate is ambiguous and + // Request gzip only, not deflate. Deflate is ambiguous and // not as universally supported anyway. // See: http://www.gzip.org/zlib/zlib_faq.html#faq38 requestedGzip = true @@ -805,24 +809,19 @@ func canonicalAddr(url *url.URL) string { return addr } -func responseIsKeepAlive(res *Response) bool { - // TODO: implement. for now just always shutting down the connection. - return false -} - // bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most // once, right before the final Read() or Close() call returns, but after // EOF has been seen. type bodyEOFSignal struct { body io.ReadCloser fn func() - isClosed bool + isClosed uint32 // atomic bool, non-zero if true once sync.Once } func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { n, err = es.body.Read(p) - if es.isClosed && n > 0 { + if es.closed() && n > 0 { panic("http: unexpected bodyEOFSignal Read after Close; see issue 1725") } if err == io.EOF { @@ -832,10 +831,10 @@ func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { } func (es *bodyEOFSignal) Close() (err error) { - if es.isClosed { + if !es.setClosed() { + // already closed return nil } - es.isClosed = true err = es.body.Close() if err == nil { es.condfn() @@ -849,6 +848,14 @@ func (es *bodyEOFSignal) condfn() { } } +func (es *bodyEOFSignal) closed() bool { + return atomic.LoadUint32(&es.isClosed) != 0 +} + +func (es *bodyEOFSignal) setClosed() bool { + return atomic.CompareAndSwapUint32(&es.isClosed, 0, 1) +} + type readFirstCloseBoth struct { io.ReadCloser io.Closer diff --git a/libgo/go/net/ipraw_test.go b/libgo/go/net/ipraw_test.go index d37272c..43b02ae 100644 --- a/libgo/go/net/ipraw_test.go +++ b/libgo/go/net/ipraw_test.go @@ -21,7 +21,7 @@ var icmpTests = []struct { ipv6 bool // test with underlying AF_INET6 socket }{ {"ip4:icmp", "", "127.0.0.1", false}, - {"ip6:icmp", "", "::1", true}, + {"ip6:ipv6-icmp", "", "::1", true}, } func TestICMP(t *testing.T) { @@ -206,3 +206,33 @@ func parseICMPEchoReply(b []byte) (id, seqnum int) { seqnum = int(b[6])<<8 | int(b[7]) return } + +var ipConnLocalNameTests = []struct { + net string + laddr *IPAddr +}{ + {"ip4:icmp", &IPAddr{IP: IPv4(127, 0, 0, 1)}}, + {"ip4:icmp", &IPAddr{}}, + {"ip4:icmp", nil}, +} + +func TestIPConnLocalName(t *testing.T) { + if os.Getuid() != 0 { + t.Logf("skipping test; must be root") + return + } + + for _, tt := range ipConnLocalNameTests { + c, err := ListenIP(tt.net, tt.laddr) + if err != nil { + t.Errorf("ListenIP failed: %v", err) + return + } + defer c.Close() + la := c.LocalAddr() + if la == nil { + t.Error("IPConn.LocalAddr failed") + return + } + } +} diff --git a/libgo/go/net/iprawsock.go b/libgo/go/net/iprawsock.go index ae21b3c..d7bffc6 100644 --- a/libgo/go/net/iprawsock.go +++ b/libgo/go/net/iprawsock.go @@ -6,6 +6,10 @@ package net +import ( + "time" +) + // IPAddr represents the address of an IP end point. type IPAddr struct { IP IP @@ -26,7 +30,11 @@ func (a *IPAddr) String() string { // "ip", "ip4" or "ip6". A literal IPv6 host address must be // enclosed in square brackets, as in "[::]". func ResolveIPAddr(net, addr string) (*IPAddr, error) { - ip, err := hostToIP(net, addr) + return resolveIPAddr(net, addr, noDeadline) +} + +func resolveIPAddr(net, addr string, deadline time.Time) (*IPAddr, error) { + ip, err := hostToIP(net, addr, deadline) if err != nil { return nil, err } @@ -34,7 +42,7 @@ func ResolveIPAddr(net, addr string) (*IPAddr, error) { } // Convert "host" into IP address. -func hostToIP(net, host string) (ip IP, err error) { +func hostToIP(net, host string, deadline time.Time) (ip IP, err error) { var addr IP // Try as an IP address. addr = ParseIP(host) @@ -47,7 +55,7 @@ func hostToIP(net, host string) (ip IP, err error) { filter = ipv6only } // Not an IP address. Try as a DNS name. - addrs, err1 := LookupHost(host) + addrs, err1 := lookupHostDeadline(host, deadline) if err1 != nil { err = err1 goto Error diff --git a/libgo/go/net/iprawsock_plan9.go b/libgo/go/net/iprawsock_plan9.go index 6de2ee3..e77c547 100644 --- a/libgo/go/net/iprawsock_plan9.go +++ b/libgo/go/net/iprawsock_plan9.go @@ -130,6 +130,10 @@ func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error // netProto, which must be "ip", "ip4", or "ip6" followed by a colon // and a protocol number or name. func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) { + return dialIP(netProto, laddr, raddr, noDeadline) +} + +func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, error) { return nil, syscall.EPLAN9 } diff --git a/libgo/go/net/iprawsock_posix.go b/libgo/go/net/iprawsock_posix.go index d0f0b56..00e87cf 100644 --- a/libgo/go/net/iprawsock_posix.go +++ b/libgo/go/net/iprawsock_posix.go @@ -10,6 +10,7 @@ package net import ( "syscall" + "time" ) func sockaddrToIP(sa syscall.Sockaddr) Addr { @@ -163,6 +164,10 @@ func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error // DialIP connects to the remote address raddr on the network protocol netProto, // which must be "ip", "ip4", or "ip6" followed by a colon and a protocol number or name. func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) { + return dialIP(netProto, laddr, raddr, noDeadline) +} + +func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, error) { net, proto, err := parseDialNetwork(netProto) if err != nil { return nil, err @@ -170,12 +175,12 @@ func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) { switch net { case "ip", "ip4", "ip6": default: - return nil, UnknownNetworkError(net) + return nil, UnknownNetworkError(netProto) } if raddr == nil { return nil, &OpError{"dial", netProto, nil, errMissingAddress} } - fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_RAW, proto, "dial", sockaddrToIP) + fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), deadline, syscall.SOCK_RAW, proto, "dial", sockaddrToIP) if err != nil { return nil, err } @@ -194,9 +199,9 @@ func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error) { switch net { case "ip", "ip4", "ip6": default: - return nil, UnknownNetworkError(net) + return nil, UnknownNetworkError(netProto) } - fd, err := internetSocket(net, laddr.toAddr(), nil, syscall.SOCK_RAW, proto, "listen", sockaddrToIP) + fd, err := internetSocket(net, laddr.toAddr(), nil, noDeadline, syscall.SOCK_RAW, proto, "listen", sockaddrToIP) if err != nil { return nil, err } diff --git a/libgo/go/net/ipsock.go b/libgo/go/net/ipsock.go index 84547c7..9d48e8c 100644 --- a/libgo/go/net/ipsock.go +++ b/libgo/go/net/ipsock.go @@ -6,7 +6,14 @@ package net -var supportsIPv6, supportsIPv4map = probeIPv6Stack() +import "time" + +var supportsIPv6, supportsIPv4map bool + +func init() { + sysInit() + supportsIPv6, supportsIPv4map = probeIPv6Stack() +} func firstFavoriteAddr(filter func(IP) IP, addrs []string) (addr IP) { if filter == nil { @@ -98,7 +105,7 @@ func JoinHostPort(host, port string) string { } // Convert "host:port" into IP address and port. -func hostPortToIP(net, hostport string) (ip IP, iport int, err error) { +func hostPortToIP(net, hostport string, deadline time.Time) (ip IP, iport int, err error) { host, port, err := SplitHostPort(hostport) if err != nil { return nil, 0, err @@ -117,7 +124,7 @@ func hostPortToIP(net, hostport string) (ip IP, iport int, err error) { filter = ipv6only } // Not an IP address. Try as a DNS name. - addrs, err := LookupHost(host) + addrs, err := lookupHostDeadline(host, deadline) if err != nil { return nil, 0, err } diff --git a/libgo/go/net/ipsock_plan9.go b/libgo/go/net/ipsock_plan9.go index 7cc2d71..138c3b4 100644 --- a/libgo/go/net/ipsock_plan9.go +++ b/libgo/go/net/ipsock_plan9.go @@ -8,10 +8,7 @@ package net import ( "errors" - "io" "os" - "syscall" - "time" ) // /sys/include/ape/sys/socket.h:/SOMAXCONN @@ -71,120 +68,6 @@ func readPlan9Addr(proto, filename string) (addr Addr, err error) { return addr, nil } -type plan9Conn struct { - proto, name, dir string - ctl, data *os.File - laddr, raddr Addr -} - -func newPlan9Conn(proto, name string, ctl *os.File, laddr, raddr Addr) *plan9Conn { - return &plan9Conn{proto, name, "/net/" + proto + "/" + name, ctl, nil, laddr, raddr} -} - -func (c *plan9Conn) ok() bool { return c != nil && c.ctl != nil } - -// Implementation of the Conn interface - see Conn for documentation. - -// Read implements the Conn Read method. -func (c *plan9Conn) Read(b []byte) (n int, err error) { - if !c.ok() { - return 0, syscall.EINVAL - } - if c.data == nil { - c.data, err = os.OpenFile(c.dir+"/data", os.O_RDWR, 0) - if err != nil { - return 0, err - } - } - n, err = c.data.Read(b) - if c.proto == "udp" && err == io.EOF { - n = 0 - err = nil - } - return -} - -// Write implements the Conn Write method. -func (c *plan9Conn) Write(b []byte) (n int, err error) { - if !c.ok() { - return 0, syscall.EINVAL - } - if c.data == nil { - c.data, err = os.OpenFile(c.dir+"/data", os.O_RDWR, 0) - if err != nil { - return 0, err - } - } - return c.data.Write(b) -} - -// Close closes the connection. -func (c *plan9Conn) Close() error { - if !c.ok() { - return syscall.EINVAL - } - err := c.ctl.Close() - if err != nil { - return err - } - if c.data != nil { - err = c.data.Close() - } - c.ctl = nil - c.data = nil - return err -} - -// LocalAddr returns the local network address. -func (c *plan9Conn) LocalAddr() Addr { - if !c.ok() { - return nil - } - return c.laddr -} - -// RemoteAddr returns the remote network address. -func (c *plan9Conn) RemoteAddr() Addr { - if !c.ok() { - return nil - } - return c.raddr -} - -// SetDeadline implements the Conn SetDeadline method. -func (c *plan9Conn) SetDeadline(t time.Time) error { - return syscall.EPLAN9 -} - -// SetReadDeadline implements the Conn SetReadDeadline method. -func (c *plan9Conn) SetReadDeadline(t time.Time) error { - return syscall.EPLAN9 -} - -// SetWriteDeadline implements the Conn SetWriteDeadline method. -func (c *plan9Conn) SetWriteDeadline(t time.Time) error { - return syscall.EPLAN9 -} - -// SetReadBuffer sets the size of the operating system's receive -// buffer associated with the connection. -func (c *plan9Conn) SetReadBuffer(bytes int) error { - return syscall.EPLAN9 -} - -// SetWriteBuffer sets the size of the operating system's transmit -// buffer associated with the connection. -func (c *plan9Conn) SetWriteBuffer(bytes int) error { - return syscall.EPLAN9 -} - -// File returns a copy of the underlying os.File, set to blocking -// mode. It is the caller's responsibility to close f when finished. -// Closing c does not affect f, and closing f does not affect c. -func (c *plan9Conn) File() (f *os.File, err error) { - return nil, syscall.EPLAN9 -} - func startPlan9(net string, addr Addr) (ctl *os.File, dest, proto, name string, err error) { var ( ip IP @@ -221,114 +104,72 @@ func startPlan9(net string, addr Addr) (ctl *os.File, dest, proto, name string, return f, dest, proto, string(buf[:n]), nil } -func dialPlan9(net string, laddr, raddr Addr) (c *plan9Conn, err error) { +func dialPlan9(net string, laddr, raddr Addr) (*netFD, error) { f, dest, proto, name, err := startPlan9(net, raddr) if err != nil { - return + return nil, err } _, err = f.WriteString("connect " + dest) if err != nil { f.Close() - return + return nil, err } laddr, err = readPlan9Addr(proto, "/net/"+proto+"/"+name+"/local") if err != nil { f.Close() - return + return nil, err } raddr, err = readPlan9Addr(proto, "/net/"+proto+"/"+name+"/remote") if err != nil { f.Close() - return + return nil, err } - return newPlan9Conn(proto, name, f, laddr, raddr), nil -} - -type plan9Listener struct { - proto, name, dir string - ctl *os.File - laddr Addr + return newFD(proto, name, f, laddr, raddr), nil } -func listenPlan9(net string, laddr Addr) (l *plan9Listener, err error) { +func listenPlan9(net string, laddr Addr) (*netFD, error) { f, dest, proto, name, err := startPlan9(net, laddr) if err != nil { - return + return nil, err } _, err = f.WriteString("announce " + dest) if err != nil { f.Close() - return + return nil, err } laddr, err = readPlan9Addr(proto, "/net/"+proto+"/"+name+"/local") if err != nil { f.Close() - return + return nil, err } - l = new(plan9Listener) - l.proto = proto - l.name = name - l.dir = "/net/" + proto + "/" + name - l.ctl = f - l.laddr = laddr - return l, nil + return &netFD{proto: proto, name: name, dir: "/net/" + proto + "/" + name, ctl: f, laddr: laddr}, nil } -func (l *plan9Listener) plan9Conn() *plan9Conn { - return newPlan9Conn(l.proto, l.name, l.ctl, l.laddr, nil) +func (l *netFD) netFD() *netFD { + return newFD(l.proto, l.name, l.ctl, l.laddr, nil) } -func (l *plan9Listener) acceptPlan9() (c *plan9Conn, err error) { +func (l *netFD) acceptPlan9() (*netFD, error) { f, err := os.Open(l.dir + "/listen") if err != nil { - return + return nil, err } var buf [16]byte n, err := f.Read(buf[:]) if err != nil { f.Close() - return + return nil, err } name := string(buf[:n]) laddr, err := readPlan9Addr(l.proto, l.dir+"/local") if err != nil { f.Close() - return + return nil, err } raddr, err := readPlan9Addr(l.proto, l.dir+"/remote") if err != nil { f.Close() - return - } - return newPlan9Conn(l.proto, name, f, laddr, raddr), nil -} - -func (l *plan9Listener) Accept() (c Conn, err error) { - c1, err := l.acceptPlan9() - if err != nil { - return + return nil, err } - return c1, nil -} - -func (l *plan9Listener) Close() error { - if l == nil || l.ctl == nil { - return syscall.EINVAL - } - return l.ctl.Close() -} - -func (l *plan9Listener) Addr() Addr { return l.laddr } - -// SetDeadline sets the deadline associated with the listener. -// A zero time value disables the deadline. -func (l *plan9Listener) SetDeadline(t time.Time) error { - return syscall.EPLAN9 -} - -// File returns a copy of the underlying os.File, set to blocking -// mode. It is the caller's responsibility to close f when finished. -// Closing l does not affect f, and closing f does not affect l. -func (l *plan9Listener) File() (f *os.File, err error) { - return nil, syscall.EPLAN9 + return newFD(l.proto, name, f, laddr, raddr), nil } diff --git a/libgo/go/net/ipsock_posix.go b/libgo/go/net/ipsock_posix.go index 1718892..87a2288 100644 --- a/libgo/go/net/ipsock_posix.go +++ b/libgo/go/net/ipsock_posix.go @@ -6,7 +6,10 @@ package net -import "syscall" +import ( + "syscall" + "time" +) // Should we try to use the IPv4 socket interface if we're // only dealing with IPv4 sockets? As long as the host system @@ -125,7 +128,7 @@ type sockaddr interface { sockaddr(family int) (syscall.Sockaddr, error) } -func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { +func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { var la, ra syscall.Sockaddr family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode) if laddr != nil { @@ -138,7 +141,7 @@ func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode s goto Error } } - fd, err = socket(net, family, sotype, proto, ipv6only, la, ra, toAddr) + fd, err = socket(net, family, sotype, proto, ipv6only, la, ra, deadline, toAddr) if err != nil { goto Error } diff --git a/libgo/go/net/lookup.go b/libgo/go/net/lookup.go index 3a44e52..bec93ec 100644 --- a/libgo/go/net/lookup.go +++ b/libgo/go/net/lookup.go @@ -4,12 +4,53 @@ package net +import ( + "time" +) + // LookupHost looks up the given host using the local resolver. // It returns an array of that host's addresses. func LookupHost(host string) (addrs []string, err error) { return lookupHost(host) } +func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err error) { + if deadline.IsZero() { + return lookupHost(host) + } + + // TODO(bradfitz): consider pushing the deadline down into the + // name resolution functions. But that involves fixing it for + // the native Go resolver, cgo, Windows, etc. + // + // In the meantime, just use a goroutine. Most users affected + // by http://golang.org/issue/2631 are due to TCP connections + // to unresponsive hosts, not DNS. + timeout := deadline.Sub(time.Now()) + if timeout <= 0 { + err = errTimeout + return + } + t := time.NewTimer(timeout) + defer t.Stop() + type res struct { + addrs []string + err error + } + resc := make(chan res, 1) + go func() { + a, err := lookupHost(host) + resc <- res{a, err} + }() + select { + case <-t.C: + err = errTimeout + case r := <-resc: + addrs, err = r.addrs, r.err + } + return +} + // LookupIP looks up host using the local resolver. // It returns an array of that host's IPv4 and IPv6 addresses. func LookupIP(host string) (addrs []IP, err error) { @@ -47,6 +88,11 @@ func LookupMX(name string) (mx []*MX, err error) { return lookupMX(name) } +// LookupNS returns the DNS NS records for the given domain name. +func LookupNS(name string) (ns []*NS, err error) { + return lookupNS(name) +} + // LookupTXT returns the DNS TXT records for the given domain name. func LookupTXT(name string) (txt []string, err error) { return lookupTXT(name) diff --git a/libgo/go/net/lookup_plan9.go b/libgo/go/net/lookup_plan9.go index 2c69830..ae7cf79 100644 --- a/libgo/go/net/lookup_plan9.go +++ b/libgo/go/net/lookup_plan9.go @@ -201,6 +201,21 @@ func lookupMX(name string) (mx []*MX, err error) { return } +func lookupNS(name string) (ns []*NS, err error) { + lines, err := queryDNS(name, "ns") + if err != nil { + return + } + for _, line := range lines { + f := getFields(line) + if len(f) < 4 { + continue + } + ns = append(ns, &NS{f[3]}) + } + return +} + func lookupTXT(name string) (txt []string, err error) { lines, err := queryDNS(name, "txt") if err != nil { diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go index 84f089e..990ade9 100644 --- a/libgo/go/net/lookup_test.go +++ b/libgo/go/net/lookup_test.go @@ -52,6 +52,20 @@ func TestGmailMX(t *testing.T) { } } +func TestGmailNS(t *testing.T) { + if testing.Short() || !*testExternal { + t.Logf("skipping test to avoid external network") + return + } + ns, err := LookupNS("gmail.com") + if err != nil { + t.Errorf("failed: %s", err) + } + if len(ns) == 0 { + t.Errorf("no results") + } +} + func TestGmailTXT(t *testing.T) { if testing.Short() || !*testExternal { t.Logf("skipping test to avoid external network") diff --git a/libgo/go/net/lookup_unix.go b/libgo/go/net/lookup_unix.go index d500a12..fa98eed 100644 --- a/libgo/go/net/lookup_unix.go +++ b/libgo/go/net/lookup_unix.go @@ -119,6 +119,19 @@ func lookupMX(name string) (mx []*MX, err error) { return } +func lookupNS(name string) (ns []*NS, err error) { + _, records, err := lookup(name, dnsTypeNS) + if err != nil { + return + } + ns = make([]*NS, len(records)) + for i, r := range records { + r := r.(*dnsRR_NS) + ns[i] = &NS{r.Ns} + } + return +} + func lookupTXT(name string) (txt []string, err error) { _, records, err := lookup(name, dnsTypeTXT) if err != nil { diff --git a/libgo/go/net/lookup_windows.go b/libgo/go/net/lookup_windows.go index 99783e9..390fe7f 100644 --- a/libgo/go/net/lookup_windows.go +++ b/libgo/go/net/lookup_windows.go @@ -40,7 +40,9 @@ func lookupHost(name string) (addrs []string, err error) { return } -func lookupIP(name string) (addrs []IP, err error) { +var lookupIP = oldLookupIP + +func oldLookupIP(name string) (addrs []IP, err error) { hostentLock.Lock() defer hostentLock.Unlock() h, err := syscall.GetHostByName(name) @@ -56,7 +58,36 @@ func lookupIP(name string) (addrs []IP, err error) { } addrs = addrs[0:i] default: // TODO(vcc): Implement non IPv4 address lookups. - return nil, os.NewSyscallError("LookupHost", syscall.EWINDOWS) + return nil, os.NewSyscallError("LookupIP", syscall.EWINDOWS) + } + return addrs, nil +} + +func newLookupIP(name string) (addrs []IP, err error) { + hints := syscall.AddrinfoW{ + Family: syscall.AF_UNSPEC, + Socktype: syscall.SOCK_STREAM, + Protocol: syscall.IPPROTO_IP, + } + var result *syscall.AddrinfoW + e := syscall.GetAddrInfoW(syscall.StringToUTF16Ptr(name), nil, &hints, &result) + if e != nil { + return nil, os.NewSyscallError("GetAddrInfoW", e) + } + defer syscall.FreeAddrInfoW(result) + addrs = make([]IP, 0, 5) + for ; result != nil; result = result.Next { + addr := unsafe.Pointer(result.Addr) + switch result.Family { + case syscall.AF_INET: + a := (*syscall.RawSockaddrInet4)(addr).Addr + addrs = append(addrs, IPv4(a[0], a[1], a[2], a[3])) + case syscall.AF_INET6: + a := (*syscall.RawSockaddrInet6)(addr).Addr + addrs = append(addrs, IP{a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]}) + default: + return nil, os.NewSyscallError("LookupIP", syscall.EWINDOWS) + } } return addrs, nil } @@ -129,6 +160,21 @@ func lookupMX(name string) (mx []*MX, err error) { return mx, nil } +func lookupNS(name string) (ns []*NS, err error) { + var r *syscall.DNSRecord + e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &r, nil) + if e != nil { + return nil, os.NewSyscallError("LookupNS", e) + } + defer syscall.DnsRecordListFree(r, 1) + ns = make([]*NS, 0, 10) + for p := r; p != nil && p.Type == syscall.DNS_TYPE_NS; p = p.Next { + v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) + ns = append(ns, &NS{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]) + "."}) + } + return ns, nil +} + func lookupTXT(name string) (txt []string, err error) { var r *syscall.DNSRecord e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil) diff --git a/libgo/go/net/multicast_posix_test.go b/libgo/go/net/multicast_posix_test.go index 3767a6b..d4a8a35 100644 --- a/libgo/go/net/multicast_posix_test.go +++ b/libgo/go/net/multicast_posix_test.go @@ -118,16 +118,29 @@ func TestSimpleMulticastListener(t *testing.T) { func checkMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) { if !multicastRIBContains(t, gaddr.IP) { - t.Fatalf("%q not found in RIB", gaddr.String()) + t.Errorf("%q not found in RIB", gaddr.String()) + return + } + la := c.LocalAddr() + if la == nil { + t.Error("LocalAddr failed") + return } - if c.LocalAddr().String() != gaddr.String() { - t.Fatalf("LocalAddr returns %q, expected %q", c.LocalAddr().String(), gaddr.String()) + if a, ok := la.(*UDPAddr); !ok || a.Port == 0 { + t.Errorf("got %v; expected a proper address with non-zero port number", la) + return } } func checkSimpleMulticastListener(t *testing.T, err error, c *UDPConn, gaddr *UDPAddr) { - if c.LocalAddr().String() != gaddr.String() { - t.Fatalf("LocalAddr returns %q, expected %q", c.LocalAddr().String(), gaddr.String()) + la := c.LocalAddr() + if la == nil { + t.Error("LocalAddr failed") + return + } + if a, ok := la.(*UDPAddr); !ok || a.Port == 0 { + t.Errorf("got %v; expected a proper address with non-zero port number", la) + return } } diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index 9ebcdbe..feb92a2 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -44,6 +44,8 @@ package net import ( "errors" + "os" + "syscall" "time" ) @@ -103,6 +105,101 @@ type Conn interface { SetWriteDeadline(t time.Time) error } +type conn struct { + fd *netFD +} + +func (c *conn) ok() bool { return c != nil && c.fd != nil } + +// Implementation of the Conn interface. + +// Read implements the Conn Read method. +func (c *conn) Read(b []byte) (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + return c.fd.Read(b) +} + +// Write implements the Conn Write method. +func (c *conn) Write(b []byte) (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + return c.fd.Write(b) +} + +// Close closes the connection. +func (c *conn) Close() error { + if !c.ok() { + return syscall.EINVAL + } + return c.fd.Close() +} + +// LocalAddr returns the local network address. +func (c *conn) LocalAddr() Addr { + if !c.ok() { + return nil + } + return c.fd.laddr +} + +// RemoteAddr returns the remote network address. +func (c *conn) RemoteAddr() Addr { + if !c.ok() { + return nil + } + return c.fd.raddr +} + +// SetDeadline implements the Conn SetDeadline method. +func (c *conn) SetDeadline(t time.Time) error { + if !c.ok() { + return syscall.EINVAL + } + return setDeadline(c.fd, t) +} + +// SetReadDeadline implements the Conn SetReadDeadline method. +func (c *conn) SetReadDeadline(t time.Time) error { + if !c.ok() { + return syscall.EINVAL + } + return setReadDeadline(c.fd, t) +} + +// SetWriteDeadline implements the Conn SetWriteDeadline method. +func (c *conn) SetWriteDeadline(t time.Time) error { + if !c.ok() { + return syscall.EINVAL + } + return setWriteDeadline(c.fd, t) +} + +// SetReadBuffer sets the size of the operating system's +// receive buffer associated with the connection. +func (c *conn) SetReadBuffer(bytes int) error { + if !c.ok() { + return syscall.EINVAL + } + return setReadBuffer(c.fd, bytes) +} + +// SetWriteBuffer sets the size of the operating system's +// transmit buffer associated with the connection. +func (c *conn) SetWriteBuffer(bytes int) error { + if !c.ok() { + return syscall.EINVAL + } + return setWriteBuffer(c.fd, bytes) +} + +// File returns a copy of the underlying os.File, set to blocking mode. +// It is the caller's responsibility to close f when finished. +// Closing c does not affect f, and closing f does not affect c. +func (c *conn) File() (f *os.File, err error) { return c.fd.dup() } + // An Error represents a network error. type Error interface { error @@ -204,6 +301,8 @@ func (e *OpError) Temporary() bool { return ok && t.Temporary() } +var noDeadline = time.Time{} + type timeout interface { Timeout() bool } @@ -221,6 +320,8 @@ func (e *timeoutError) Temporary() bool { return true } var errTimeout error = &timeoutError{} +var errClosing = errors.New("use of closed network connection") + type AddrError struct { Err string Addr string diff --git a/libgo/go/net/net_posix.go b/libgo/go/net/net_posix.go deleted file mode 100644 index 3bcc54f..0000000 --- a/libgo/go/net/net_posix.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2012 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. - -// +build darwin freebsd linux netbsd openbsd windows - -// Base posix socket functions. - -package net - -import ( - "os" - "syscall" - "time" -) - -type conn struct { - fd *netFD -} - -func (c *conn) ok() bool { return c != nil && c.fd != nil } - -// Implementation of the Conn interface - see Conn for documentation. - -// Read implements the Conn Read method. -func (c *conn) Read(b []byte) (int, error) { - if !c.ok() { - return 0, syscall.EINVAL - } - return c.fd.Read(b) -} - -// Write implements the Conn Write method. -func (c *conn) Write(b []byte) (int, error) { - if !c.ok() { - return 0, syscall.EINVAL - } - return c.fd.Write(b) -} - -// LocalAddr returns the local network address. -func (c *conn) LocalAddr() Addr { - if !c.ok() { - return nil - } - return c.fd.laddr -} - -// RemoteAddr returns the remote network address. -func (c *conn) RemoteAddr() Addr { - if !c.ok() { - return nil - } - return c.fd.raddr -} - -// SetDeadline implements the Conn SetDeadline method. -func (c *conn) SetDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - return setDeadline(c.fd, t) -} - -// SetReadDeadline implements the Conn SetReadDeadline method. -func (c *conn) SetReadDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - return setReadDeadline(c.fd, t) -} - -// SetWriteDeadline implements the Conn SetWriteDeadline method. -func (c *conn) SetWriteDeadline(t time.Time) error { - if !c.ok() { - return syscall.EINVAL - } - return setWriteDeadline(c.fd, t) -} - -// SetReadBuffer sets the size of the operating system's -// receive buffer associated with the connection. -func (c *conn) SetReadBuffer(bytes int) error { - if !c.ok() { - return syscall.EINVAL - } - return setReadBuffer(c.fd, bytes) -} - -// SetWriteBuffer sets the size of the operating system's -// transmit buffer associated with the connection. -func (c *conn) SetWriteBuffer(bytes int) error { - if !c.ok() { - return syscall.EINVAL - } - return setWriteBuffer(c.fd, bytes) -} - -// File returns a copy of the underlying os.File, set to blocking mode. -// It is the caller's responsibility to close f when finished. -// Closing c does not affect f, and closing f does not affect c. -func (c *conn) File() (f *os.File, err error) { return c.fd.dup() } - -// Close closes the connection. -func (c *conn) Close() error { - if !c.ok() { - return syscall.EINVAL - } - return c.fd.Close() -} diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go index 623a788..a4e8dcd 100644 --- a/libgo/go/net/net_test.go +++ b/libgo/go/net/net_test.go @@ -174,3 +174,42 @@ func TestUDPListenClose(t *testing.T) { t.Fatal("timeout waiting for UDP close") } } + +func TestTCPClose(t *testing.T) { + l, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + read := func(r io.Reader) error { + var m [1]byte + _, err := r.Read(m[:]) + return err + } + + go func() { + c, err := Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatal(err) + } + + go read(c) + + time.Sleep(10 * time.Millisecond) + c.Close() + }() + + c, err := l.Accept() + if err != nil { + t.Fatal(err) + } + defer c.Close() + + for err == nil { + err = read(c) + } + if err != nil && err != io.EOF { + t.Fatal(err) + } +} diff --git a/libgo/go/net/packetconn_test.go b/libgo/go/net/packetconn_test.go new file mode 100644 index 0000000..5075baa --- /dev/null +++ b/libgo/go/net/packetconn_test.go @@ -0,0 +1,161 @@ +// Copyright 2012 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_test + +import ( + "net" + "os" + "runtime" + "strings" + "testing" + "time" +) + +var packetConnTests = []struct { + net string + addr1 string + addr2 string +}{ + {"udp", "127.0.0.1:0", "127.0.0.1:0"}, + {"ip:icmp", "127.0.0.1", "127.0.0.1"}, + {"unixgram", "/tmp/gotest.net1", "/tmp/gotest.net2"}, +} + +func TestPacketConn(t *testing.T) { + for _, tt := range packetConnTests { + var wb []byte + netstr := strings.Split(tt.net, ":") + switch netstr[0] { + case "udp": + wb = []byte("UDP PACKETCONN TEST") + case "ip": + switch runtime.GOOS { + case "plan9": + continue + } + if os.Getuid() != 0 { + continue + } + id := os.Getpid() & 0xffff + wb = newICMPEchoRequest(id, 1, 128, []byte("IP PACKETCONN TEST ")) + case "unixgram": + switch runtime.GOOS { + case "plan9", "windows": + continue + } + os.Remove(tt.addr1) + os.Remove(tt.addr2) + wb = []byte("UNIXGRAM PACKETCONN TEST") + default: + continue + } + + c1, err := net.ListenPacket(tt.net, tt.addr1) + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + c1.LocalAddr() + c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c1.Close() + + c2, err := net.ListenPacket(tt.net, tt.addr2) + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + c2.LocalAddr() + c2.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c2.Close() + + if _, err := c1.WriteTo(wb, c2.LocalAddr()); err != nil { + t.Fatalf("net.PacketConn.WriteTo failed: %v", err) + } + rb2 := make([]byte, 128) + if _, _, err := c2.ReadFrom(rb2); err != nil { + t.Fatalf("net.PacketConn.ReadFrom failed: %v", err) + } + if _, err := c2.WriteTo(wb, c1.LocalAddr()); err != nil { + t.Fatalf("net.PacketConn.WriteTo failed: %v", err) + } + rb1 := make([]byte, 128) + if _, _, err := c1.ReadFrom(rb1); err != nil { + t.Fatalf("net.PacketConn.ReadFrom failed: %v", err) + } + + switch netstr[0] { + case "unixgram": + os.Remove(tt.addr1) + os.Remove(tt.addr2) + } + } +} + +func TestConnAndPacketConn(t *testing.T) { + for _, tt := range packetConnTests { + var wb []byte + netstr := strings.Split(tt.net, ":") + switch netstr[0] { + case "udp": + wb = []byte("UDP PACKETCONN TEST") + case "ip": + switch runtime.GOOS { + case "plan9": + continue + } + if os.Getuid() != 0 { + continue + } + id := os.Getpid() & 0xffff + wb = newICMPEchoRequest(id, 1, 128, []byte("IP PACKETCONN TEST")) + default: + continue + } + + c1, err := net.ListenPacket(tt.net, tt.addr1) + if err != nil { + t.Fatalf("net.ListenPacket failed: %v", err) + } + c1.LocalAddr() + c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c1.Close() + + c2, err := net.Dial(tt.net, c1.LocalAddr().String()) + if err != nil { + t.Fatalf("net.Dial failed: %v", err) + } + c2.LocalAddr() + c2.RemoteAddr() + c2.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c2.Close() + + if _, err := c2.Write(wb); err != nil { + t.Fatalf("net.Conn.Write failed: %v", err) + } + rb1 := make([]byte, 128) + if _, _, err := c1.ReadFrom(rb1); err != nil { + t.Fatalf("net.PacetConn.ReadFrom failed: %v", err) + } + var dst net.Addr + if netstr[0] == "ip" { + dst = &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} + } else { + dst = c2.LocalAddr() + } + if _, err := c1.WriteTo(wb, dst); err != nil { + t.Fatalf("net.PacketConn.WriteTo failed: %v", err) + } + rb2 := make([]byte, 128) + if _, err := c2.Read(rb2); err != nil { + t.Fatalf("net.Conn.Read failed: %v", err) + } + } +} diff --git a/libgo/go/net/protoconn_test.go b/libgo/go/net/protoconn_test.go new file mode 100644 index 0000000..f249372 --- /dev/null +++ b/libgo/go/net/protoconn_test.go @@ -0,0 +1,372 @@ +// Copyright 2012 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_test + +import ( + "bytes" + "net" + "os" + "runtime" + "testing" + "time" +) + +var condErrorf = func() func(*testing.T, string, ...interface{}) { + // A few APIs are not implemented yet on both Plan 9 and Windows. + switch runtime.GOOS { + case "plan9", "windows": + return (*testing.T).Logf + } + return (*testing.T).Errorf +}() + +func TestTCPListenerSpecificMethods(t *testing.T) { + la, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.ResolveTCPAddr failed: %v", err) + } + ln, err := net.ListenTCP("tcp4", la) + if err != nil { + t.Fatalf("net.ListenTCP failed: %v", err) + } + ln.Addr() + ln.SetDeadline(time.Now().Add(30 * time.Nanosecond)) + defer ln.Close() + + if c, err := ln.Accept(); err != nil { + if !err.(net.Error).Timeout() { + t.Errorf("net.TCPListener.Accept failed: %v", err) + return + } + } else { + c.Close() + } + if c, err := ln.AcceptTCP(); err != nil { + if !err.(net.Error).Timeout() { + t.Errorf("net.TCPListener.AcceptTCP failed: %v", err) + return + } + } else { + c.Close() + } + + if f, err := ln.File(); err != nil { + condErrorf(t, "net.TCPListener.File failed: %v", err) + return + } else { + f.Close() + } +} + +func TestTCPConnSpecificMethods(t *testing.T) { + la, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.ResolveTCPAddr failed: %v", err) + } + ln, err := net.ListenTCP("tcp4", la) + if err != nil { + t.Fatalf("net.ListenTCP failed: %v", err) + } + ln.Addr() + defer ln.Close() + + done := make(chan int) + go transponder(t, ln, done) + + ra, err := net.ResolveTCPAddr("tcp4", ln.Addr().String()) + if err != nil { + t.Errorf("net.ResolveTCPAddr failed: %v", err) + return + } + c, err := net.DialTCP("tcp4", nil, ra) + if err != nil { + t.Errorf("net.DialTCP failed: %v", err) + return + } + c.SetKeepAlive(false) + c.SetLinger(0) + c.SetNoDelay(false) + c.LocalAddr() + c.RemoteAddr() + c.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + defer c.Close() + + if _, err := c.Write([]byte("TCPCONN TEST")); err != nil { + t.Errorf("net.TCPConn.Write failed: %v", err) + return + } + rb := make([]byte, 128) + if _, err := c.Read(rb); err != nil { + t.Errorf("net.TCPConn.Read failed: %v", err) + return + } + + <-done +} + +func TestUDPConnSpecificMethods(t *testing.T) { + la, err := net.ResolveUDPAddr("udp4", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.ResolveUDPAddr failed: %v", err) + } + c, err := net.ListenUDP("udp4", la) + if err != nil { + t.Fatalf("net.ListenUDP failed: %v", err) + } + c.LocalAddr() + c.RemoteAddr() + c.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadBuffer(2048) + c.SetWriteBuffer(2048) + defer c.Close() + + wb := []byte("UDPCONN TEST") + rb := make([]byte, 128) + if _, err := c.WriteToUDP(wb, c.LocalAddr().(*net.UDPAddr)); err != nil { + t.Errorf("net.UDPConn.WriteToUDP failed: %v", err) + return + } + if _, _, err := c.ReadFromUDP(rb); err != nil { + t.Errorf("net.UDPConn.ReadFromUDP failed: %v", err) + return + } + if _, _, err := c.WriteMsgUDP(wb, nil, c.LocalAddr().(*net.UDPAddr)); err != nil { + condErrorf(t, "net.UDPConn.WriteMsgUDP failed: %v", err) + return + } + if _, _, _, _, err := c.ReadMsgUDP(rb, nil); err != nil { + condErrorf(t, "net.UDPConn.ReadMsgUDP failed: %v", err) + return + } + + if f, err := c.File(); err != nil { + condErrorf(t, "net.UDPConn.File failed: %v", err) + return + } else { + f.Close() + } +} + +func TestIPConnSpecificMethods(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Logf("skipping read test on %q", runtime.GOOS) + return + } + if os.Getuid() != 0 { + t.Logf("skipping test; must be root") + return + } + + la, err := net.ResolveIPAddr("ip4", "127.0.0.1") + if err != nil { + t.Fatalf("net.ResolveIPAddr failed: %v", err) + } + c, err := net.ListenIP("ip4:icmp", la) + if err != nil { + t.Fatalf("net.ListenIP failed: %v", err) + } + c.LocalAddr() + c.RemoteAddr() + c.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + c.SetReadBuffer(2048) + c.SetWriteBuffer(2048) + defer c.Close() + + id := os.Getpid() & 0xffff + wb := newICMPEchoRequest(id, 1, 128, []byte("IPCONN TEST ")) + rb := make([]byte, 20+128) + if _, err := c.WriteToIP(wb, c.LocalAddr().(*net.IPAddr)); err != nil { + t.Errorf("net.IPConn.WriteToIP failed: %v", err) + return + } + if _, _, err := c.ReadFromIP(rb); err != nil { + t.Errorf("net.IPConn.ReadFromIP failed: %v", err) + return + } + if _, _, err := c.WriteMsgIP(wb, nil, c.LocalAddr().(*net.IPAddr)); err != nil { + condErrorf(t, "net.UDPConn.WriteMsgIP failed: %v", err) + return + } + if _, _, _, _, err := c.ReadMsgIP(rb, nil); err != nil { + condErrorf(t, "net.UDPConn.ReadMsgIP failed: %v", err) + return + } + + if f, err := c.File(); err != nil { + condErrorf(t, "net.IPConn.File failed: %v", err) + return + } else { + f.Close() + } +} + +func TestUnixListenerSpecificMethods(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Logf("skipping read test on %q", runtime.GOOS) + return + } + + p := "/tmp/gotest.net" + os.Remove(p) + la, err := net.ResolveUnixAddr("unix", p) + if err != nil { + t.Fatalf("net.ResolveUnixAddr failed: %v", err) + } + ln, err := net.ListenUnix("unix", la) + if err != nil { + t.Fatalf("net.ListenUnix failed: %v", err) + } + ln.Addr() + ln.SetDeadline(time.Now().Add(30 * time.Nanosecond)) + defer ln.Close() + defer os.Remove(p) + + if c, err := ln.Accept(); err != nil { + if !err.(net.Error).Timeout() { + t.Errorf("net.TCPListener.AcceptTCP failed: %v", err) + return + } + } else { + c.Close() + } + if c, err := ln.AcceptUnix(); err != nil { + if !err.(net.Error).Timeout() { + t.Errorf("net.TCPListener.AcceptTCP failed: %v", err) + return + } + } else { + c.Close() + } + + if f, err := ln.File(); err != nil { + t.Errorf("net.UnixListener.File failed: %v", err) + return + } else { + f.Close() + } +} + +func TestUnixConnSpecificMethods(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Logf("skipping test on %q", runtime.GOOS) + return + } + + p1, p2 := "/tmp/gotest.net1", "/tmp/gotest.net2" + os.Remove(p1) + os.Remove(p2) + + a1, err := net.ResolveUnixAddr("unixgram", p1) + if err != nil { + t.Fatalf("net.ResolveUnixAddr failed: %v", err) + } + c1, err := net.DialUnix("unixgram", a1, nil) + if err != nil { + t.Fatalf("net.DialUnix failed: %v", err) + } + c1.LocalAddr() + c1.RemoteAddr() + c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + c1.SetReadBuffer(2048) + c1.SetWriteBuffer(2048) + defer c1.Close() + defer os.Remove(p1) + + a2, err := net.ResolveUnixAddr("unixgram", p2) + if err != nil { + t.Errorf("net.ResolveUnixAddr failed: %v", err) + return + } + c2, err := net.DialUnix("unixgram", a2, nil) + if err != nil { + t.Errorf("net.DialUnix failed: %v", err) + return + } + c2.LocalAddr() + c2.RemoteAddr() + c2.SetDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + c2.SetReadBuffer(2048) + c2.SetWriteBuffer(2048) + defer c2.Close() + defer os.Remove(p2) + + wb := []byte("UNIXCONN TEST") + rb1 := make([]byte, 128) + rb2 := make([]byte, 128) + if _, _, err := c1.WriteMsgUnix(wb, nil, a2); err != nil { + t.Errorf("net.UnixConn.WriteMsgUnix failed: %v", err) + return + } + if _, _, _, _, err := c2.ReadMsgUnix(rb2, nil); err != nil { + t.Errorf("net.UnixConn.ReadMsgUnix failed: %v", err) + return + } + if _, err := c2.WriteToUnix(wb, a1); err != nil { + t.Errorf("net.UnixConn.WriteToUnix failed: %v", err) + return + } + if _, _, err := c1.ReadFromUnix(rb1); err != nil { + t.Errorf("net.UnixConn.ReadFromUnix failed: %v", err) + return + } + + // TODO: http://golang.org/issue/3875 + net.ListenUnixgram("unixgram", nil) + + if f, err := c1.File(); err != nil { + t.Errorf("net.UnixConn.File failed: %v", err) + return + } else { + f.Close() + } +} + +func newICMPEchoRequest(id, seqnum, msglen int, filler []byte) []byte { + b := newICMPInfoMessage(id, seqnum, msglen, filler) + b[0] = 8 + // calculate ICMP checksum + cklen := len(b) + s := uint32(0) + for i := 0; i < cklen-1; i += 2 { + s += uint32(b[i+1])<<8 | uint32(b[i]) + } + if cklen&1 == 1 { + s += uint32(b[cklen-1]) + } + s = (s >> 16) + (s & 0xffff) + s = s + (s >> 16) + // place checksum back in header; using ^= avoids the + // assumption the checksum bytes are zero + b[2] ^= byte(^s & 0xff) + b[3] ^= byte(^s >> 8) + return b +} + +func newICMPInfoMessage(id, seqnum, msglen int, filler []byte) []byte { + b := make([]byte, msglen) + copy(b[8:], bytes.Repeat(filler, (msglen-8)/len(filler)+1)) + b[0] = 0 // type + b[1] = 0 // code + b[2] = 0 // checksum + b[3] = 0 // checksum + b[4] = byte(id >> 8) // identifier + b[5] = byte(id & 0xff) // identifier + b[6] = byte(seqnum >> 8) // sequence number + b[7] = byte(seqnum & 0xff) // sequence number + return b +} diff --git a/libgo/go/net/rpc/client.go b/libgo/go/net/rpc/client.go index 7d3d0bb..ee3cc4d 100644 --- a/libgo/go/net/rpc/client.go +++ b/libgo/go/net/rpc/client.go @@ -228,7 +228,7 @@ func DialHTTP(network, address string) (*Client, error) { return DialHTTPPath(network, address, DefaultRPCPath) } -// DialHTTPPath connects to an HTTP RPC server +// DialHTTPPath connects to an HTTP RPC server // at the specified network address and path. func DialHTTPPath(network, address, path string) (*Client, error) { var err error diff --git a/libgo/go/net/rpc/server.go b/libgo/go/net/rpc/server.go index e528220..8898b98 100644 --- a/libgo/go/net/rpc/server.go +++ b/libgo/go/net/rpc/server.go @@ -227,7 +227,7 @@ func (server *Server) Register(rcvr interface{}) error { return server.register(rcvr, "", false) } -// RegisterName is like Register but uses the provided name for the type +// RegisterName is like Register but uses the provided name for the type // instead of the receiver's concrete type. func (server *Server) RegisterName(name string, rcvr interface{}) error { return server.register(rcvr, name, true) @@ -261,8 +261,30 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro s.method = make(map[string]*methodType) // Install the methods - for m := 0; m < s.typ.NumMethod(); m++ { - method := s.typ.Method(m) + s.method = suitableMethods(s.typ, true) + + if len(s.method) == 0 { + str := "" + // To help the user, see if a pointer receiver would work. + method := suitableMethods(reflect.PtrTo(s.typ), false) + if len(method) != 0 { + str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)" + } else { + str = "rpc.Register: type " + sname + " has no exported methods of suitable type" + } + log.Print(str) + return errors.New(str) + } + server.serviceMap[s.name] = s + return nil +} + +// suitableMethods returns suitable Rpc methods of typ, it will report +// error using log if reportErr is true. +func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType { + methods := make(map[string]*methodType) + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) mtype := method.Type mname := method.Name // Method must be exported. @@ -271,46 +293,51 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro } // Method needs three ins: receiver, *args, *reply. if mtype.NumIn() != 3 { - log.Println("method", mname, "has wrong number of ins:", mtype.NumIn()) + if reportErr { + log.Println("method", mname, "has wrong number of ins:", mtype.NumIn()) + } continue } // First arg need not be a pointer. argType := mtype.In(1) if !isExportedOrBuiltinType(argType) { - log.Println(mname, "argument type not exported:", argType) + if reportErr { + log.Println(mname, "argument type not exported:", argType) + } continue } // Second arg must be a pointer. replyType := mtype.In(2) if replyType.Kind() != reflect.Ptr { - log.Println("method", mname, "reply type not a pointer:", replyType) + if reportErr { + log.Println("method", mname, "reply type not a pointer:", replyType) + } continue } // Reply type must be exported. if !isExportedOrBuiltinType(replyType) { - log.Println("method", mname, "reply type not exported:", replyType) + if reportErr { + log.Println("method", mname, "reply type not exported:", replyType) + } continue } // Method needs one out. if mtype.NumOut() != 1 { - log.Println("method", mname, "has wrong number of outs:", mtype.NumOut()) + if reportErr { + log.Println("method", mname, "has wrong number of outs:", mtype.NumOut()) + } continue } // The return type of the method must be error. if returnType := mtype.Out(0); returnType != typeOfError { - log.Println("method", mname, "returns", returnType.String(), "not error") + if reportErr { + log.Println("method", mname, "returns", returnType.String(), "not error") + } continue } - s.method[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType} - } - - if len(s.method) == 0 { - s := "rpc Register: type " + sname + " has no exported methods of suitable type" - log.Print(s) - return errors.New(s) + methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType} } - server.serviceMap[s.name] = s - return nil + return methods } // A value sent as a placeholder for the server's response value when the server @@ -569,7 +596,7 @@ func (server *Server) Accept(lis net.Listener) { // Register publishes the receiver's methods in the DefaultServer. func Register(rcvr interface{}) error { return DefaultServer.Register(rcvr) } -// RegisterName is like Register but uses the provided name for the type +// RegisterName is like Register but uses the provided name for the type // instead of the receiver's concrete type. func RegisterName(name string, rcvr interface{}) error { return DefaultServer.RegisterName(name, rcvr) @@ -612,7 +639,7 @@ func ServeRequest(codec ServerCodec) error { } // Accept accepts connections on the listener and serves requests -// to DefaultServer for each incoming connection. +// to DefaultServer for each incoming connection. // Accept blocks; the caller typically invokes it in a go statement. func Accept(lis net.Listener) { DefaultServer.Accept(lis) } diff --git a/libgo/go/net/rpc/server_test.go b/libgo/go/net/rpc/server_test.go index 62c7b1e..d9ebe71 100644 --- a/libgo/go/net/rpc/server_test.go +++ b/libgo/go/net/rpc/server_test.go @@ -349,6 +349,7 @@ func testServeRequest(t *testing.T, server *Server) { type ReplyNotPointer int type ArgNotPublic int type ReplyNotPublic int +type NeedsPtrType int type local struct{} func (t *ReplyNotPointer) ReplyNotPointer(args *Args, reply Reply) error { @@ -363,19 +364,29 @@ func (t *ReplyNotPublic) ReplyNotPublic(args *Args, reply *local) error { return nil } +func (t *NeedsPtrType) NeedsPtrType(args *Args, reply *Reply) error { + return nil +} + // Check that registration handles lots of bad methods and a type with no suitable methods. func TestRegistrationError(t *testing.T) { err := Register(new(ReplyNotPointer)) if err == nil { - t.Errorf("expected error registering ReplyNotPointer") + t.Error("expected error registering ReplyNotPointer") } err = Register(new(ArgNotPublic)) if err == nil { - t.Errorf("expected error registering ArgNotPublic") + t.Error("expected error registering ArgNotPublic") } err = Register(new(ReplyNotPublic)) if err == nil { - t.Errorf("expected error registering ReplyNotPublic") + t.Error("expected error registering ReplyNotPublic") + } + err = Register(NeedsPtrType(0)) + if err == nil { + t.Error("expected error registering NeedsPtrType") + } else if !strings.Contains(err.Error(), "pointer") { + t.Error("expected hint when registering NeedsPtrType") } } @@ -499,6 +510,27 @@ func TestClientWriteError(t *testing.T) { w.done <- true } +func TestTCPClose(t *testing.T) { + once.Do(startServer) + + client, err := dialHTTP() + if err != nil { + t.Fatalf("dialing: %v", err) + } + defer client.Close() + + args := Args{17, 8} + var reply Reply + err = client.Call("Arith.Mul", args, &reply) + if err != nil { + t.Fatal("arith error:", err) + } + t.Logf("Arith: %d*%d=%d\n", args.A, args.B, reply) + if reply.C != args.A*args.B { + t.Errorf("Add: expected %d got %d", reply.C, args.A*args.B) + } +} + func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) { b.StopTimer() once.Do(startServer) diff --git a/libgo/go/net/sendfile_windows.go b/libgo/go/net/sendfile_windows.go index f5a6d88..2d64f2f 100644 --- a/libgo/go/net/sendfile_windows.go +++ b/libgo/go/net/sendfile_windows.go @@ -48,12 +48,12 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { return 0, nil, false } - c.wio.Lock() - defer c.wio.Unlock() if err := c.incref(false); err != nil { return 0, err, true } defer c.decref() + c.wio.Lock() + defer c.wio.Unlock() var o sendfileOp o.Init(c, 'w') diff --git a/libgo/go/net/sock_posix.go b/libgo/go/net/sock_posix.go index dc5247a..78417fd 100644 --- a/libgo/go/net/sock_posix.go +++ b/libgo/go/net/sock_posix.go @@ -11,12 +11,13 @@ package net import ( "io" "syscall" + "time" ) var listenerBacklog = maxListenerBacklog() // Generic socket creation. -func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { +func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { // See ../syscall/exec_unix.go for description of ForkLock. syscall.ForkLock.RLock() s, err := syscall.Socket(f, t, p) @@ -32,13 +33,19 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr, return nil, err } - var blsa syscall.Sockaddr if ulsa != nil { - if blsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil { + // We provide a socket that listens to a wildcard + // address with reusable UDP port when the given ulsa + // is an appropriate UDP multicast address prefix. + // This makes it possible for a single UDP listener + // to join multiple different group addresses, for + // multiple UDP listeners that listen on the same UDP + // port to join the same group address. + if ulsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil { closesocket(s) return nil, err } - if err = syscall.Bind(s, blsa); err != nil { + if err = syscall.Bind(s, ulsa); err != nil { closesocket(s) return nil, err } @@ -50,21 +57,20 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr, } if ursa != nil { + if !deadline.IsZero() { + fd.wdeadline = deadline.UnixNano() + } if err = fd.connect(ursa); err != nil { closesocket(s) fd.Close() return nil, err } fd.isConnected = true + fd.wdeadline = 0 } lsa, _ := syscall.Getsockname(s) - var laddr Addr - if ulsa != nil && blsa != ulsa { - laddr = toAddr(ulsa) - } else { - laddr = toAddr(lsa) - } + laddr := toAddr(lsa) rsa, _ := syscall.Getpeername(s) raddr := toAddr(rsa) fd.setAddr(laddr, raddr) diff --git a/libgo/go/net/tcp_test.go b/libgo/go/net/tcp_test.go index 53daf5b..f6e4df3 100644 --- a/libgo/go/net/tcp_test.go +++ b/libgo/go/net/tcp_test.go @@ -116,3 +116,33 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool) { sem <- true } } + +var tcpListenerNameTests = []struct { + net string + laddr *TCPAddr +}{ + {"tcp4", &TCPAddr{IP: IPv4(127, 0, 0, 1)}}, + {"tcp4", &TCPAddr{}}, + {"tcp4", nil}, +} + +func TestTCPListenerName(t *testing.T) { + if testing.Short() || !*testExternal { + t.Logf("skipping test to avoid external network") + return + } + + for _, tt := range tcpListenerNameTests { + ln, err := ListenTCP(tt.net, tt.laddr) + if err != nil { + t.Errorf("ListenTCP failed: %v", err) + return + } + defer ln.Close() + la := ln.Addr() + if a, ok := la.(*TCPAddr); !ok || a.Port == 0 { + t.Errorf("got %v; expected a proper address with non-zero port number", la) + return + } + } +} diff --git a/libgo/go/net/tcpsock.go b/libgo/go/net/tcpsock.go index 47fbf29..6aba1f8 100644 --- a/libgo/go/net/tcpsock.go +++ b/libgo/go/net/tcpsock.go @@ -6,6 +6,8 @@ package net +import "time" + // TCPAddr represents the address of a TCP end point. type TCPAddr struct { IP IP @@ -28,7 +30,11 @@ func (a *TCPAddr) String() string { // "tcp4" or "tcp6". A literal IPv6 host address must be // enclosed in square brackets, as in "[::]:80". func ResolveTCPAddr(net, addr string) (*TCPAddr, error) { - ip, port, err := hostPortToIP(net, addr) + return resolveTCPAddr(net, addr, noDeadline) +} + +func resolveTCPAddr(net, addr string, deadline time.Time) (*TCPAddr, error) { + ip, port, err := hostPortToIP(net, addr, deadline) if err != nil { return nil, err } diff --git a/libgo/go/net/tcpsock_plan9.go b/libgo/go/net/tcpsock_plan9.go index 4121dd8..cec5bd2 100644 --- a/libgo/go/net/tcpsock_plan9.go +++ b/libgo/go/net/tcpsock_plan9.go @@ -6,12 +6,26 @@ package net -import "syscall" +import ( + "io" + "os" + "syscall" + "time" +) // TCPConn is an implementation of the Conn interface for TCP network // connections. type TCPConn struct { - plan9Conn + conn +} + +func newTCPConn(fd *netFD) *TCPConn { + return &TCPConn{conn{fd}} +} + +// ReadFrom implements the io.ReaderFrom ReadFrom method. +func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) { + return 0, syscall.EPLAN9 } // CloseRead shuts down the reading side of the TCP connection. @@ -20,7 +34,7 @@ func (c *TCPConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return syscall.EPLAN9 + return c.fd.CloseRead() } // CloseWrite shuts down the writing side of the TCP connection. @@ -29,6 +43,35 @@ func (c *TCPConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } + return c.fd.CloseWrite() +} + +// 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. +func (c *TCPConn) SetLinger(sec int) error { + return syscall.EPLAN9 +} + +// SetKeepAlive sets whether the operating system should send +// keepalive messages on the connection. +func (c *TCPConn) SetKeepAlive(keepalive bool) error { + return syscall.EPLAN9 +} + +// SetNoDelay controls whether the operating system should delay +// packet transmission in hopes of sending fewer packets (Nagle's +// algorithm). The default is true (no delay), meaning that data is +// sent as soon as possible after a Write. +func (c *TCPConn) SetNoDelay(noDelay bool) error { return syscall.EPLAN9 } @@ -36,6 +79,13 @@ func (c *TCPConn) CloseWrite() error { // which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is // used as the local address for the connection. func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error) { + return dialTCP(net, laddr, raddr, noDeadline) +} + +func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { + if !deadline.IsZero() { + panic("net.dialTCP: deadline not implemented on Plan 9") + } switch net { case "tcp", "tcp4", "tcp6": default: @@ -44,46 +94,91 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error) { if raddr == nil { return nil, &OpError{"dial", net, nil, errMissingAddress} } - c1, err := dialPlan9(net, laddr, raddr) + fd, err := dialPlan9(net, laddr, raddr) if err != nil { - return + return nil, err } - return &TCPConn{*c1}, nil + return &TCPConn{conn{fd}}, nil } // TCPListener is a TCP network listener. Clients should typically // use variables of type Listener instead of assuming TCP. type TCPListener struct { - plan9Listener + fd *netFD } +// AcceptTCP accepts the next incoming call and returns the new +// connection and the remote address. +func (l *TCPListener) AcceptTCP() (*TCPConn, error) { + if l == nil || l.fd == nil || l.fd.ctl == nil { + return nil, syscall.EINVAL + } + fd, err := l.fd.acceptPlan9() + if err != nil { + return nil, err + } + return newTCPConn(fd), nil +} + +// Accept implements the Accept method in the Listener interface; it +// waits for the next call and returns a generic Conn. +func (l *TCPListener) Accept() (Conn, error) { + if l == nil || l.fd == nil || l.fd.ctl == nil { + return nil, syscall.EINVAL + } + c, err := l.AcceptTCP() + if err != nil { + return nil, err + } + return c, nil +} + +// Close stops listening on the TCP address. +// Already Accepted connections are not closed. func (l *TCPListener) Close() error { - if l == nil || l.ctl == nil { + if l == nil || l.fd == nil || l.fd.ctl == nil { return syscall.EINVAL } - if _, err := l.ctl.WriteString("hangup"); err != nil { - l.ctl.Close() + if _, err := l.fd.ctl.WriteString("hangup"); err != nil { + l.fd.ctl.Close() return err } - return l.ctl.Close() + return l.fd.ctl.Close() +} + +// Addr returns the listener's network address, a *TCPAddr. +func (l *TCPListener) Addr() Addr { return l.fd.laddr } + +// SetDeadline sets the deadline associated with the listener. +// A zero time value disables the deadline. +func (l *TCPListener) SetDeadline(t time.Time) error { + if l == nil || l.fd == nil || l.fd.ctl == nil { + return syscall.EINVAL + } + return setDeadline(l.fd, t) } +// File returns a copy of the underlying os.File, set to blocking +// mode. It is the caller's responsibility to close f when finished. +// Closing l does not affect f, and closing f does not affect l. +func (l *TCPListener) File() (f *os.File, err error) { return l.fd.dup() } + // ListenTCP announces on the TCP address laddr and returns a TCP // listener. Net must be "tcp", "tcp4", or "tcp6". If laddr has a // port of 0, it means to listen on some available port. The caller // can use l.Addr() to retrieve the chosen address. -func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err error) { +func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { switch net { case "tcp", "tcp4", "tcp6": default: return nil, UnknownNetworkError(net) } if laddr == nil { - return nil, &OpError{"listen", net, nil, errMissingAddress} + laddr = &TCPAddr{} } - l1, err := listenPlan9(net, laddr) + fd, err := listenPlan9(net, laddr) if err != nil { - return + return nil, err } - return &TCPListener{*l1}, nil + return &TCPListener{fd}, nil } diff --git a/libgo/go/net/tcpsock_posix.go b/libgo/go/net/tcpsock_posix.go index 2c34d2f..e5b3a09 100644 --- a/libgo/go/net/tcpsock_posix.go +++ b/libgo/go/net/tcpsock_posix.go @@ -143,11 +143,19 @@ func (c *TCPConn) SetNoDelay(noDelay bool) error { // which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used // as the local address for the connection. func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) { + switch net { + case "tcp", "tcp4", "tcp6": + default: + return nil, UnknownNetworkError(net) + } if raddr == nil { return nil, &OpError{"dial", net, nil, errMissingAddress} } + return dialTCP(net, laddr, raddr, noDeadline) +} - fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) +func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { + fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) // TCP has a rarely used mechanism called a 'simultaneous connection' in // which Dial("tcp", addr1, addr2) run on the machine at addr1 can @@ -177,7 +185,7 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) { if err == nil { fd.Close() } - fd, err = internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) + fd, err = internetSocket(net, laddr.toAddr(), raddr.toAddr(), deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) } if err != nil { @@ -220,29 +228,10 @@ type TCPListener struct { fd *netFD } -// ListenTCP announces on the TCP address laddr and returns a TCP listener. -// Net must be "tcp", "tcp4", or "tcp6". -// If laddr has a port of 0, it means to listen on some available port. -// The caller can use l.Addr() to retrieve the chosen address. -func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { - fd, err := internetSocket(net, laddr.toAddr(), nil, syscall.SOCK_STREAM, 0, "listen", sockaddrToTCP) - if err != nil { - return nil, err - } - err = syscall.Listen(fd.sysfd, listenerBacklog) - if err != nil { - closesocket(fd.sysfd) - return nil, &OpError{"listen", net, laddr, err} - } - l := new(TCPListener) - l.fd = fd - return l, nil -} - // AcceptTCP accepts the next incoming call and returns the new connection // and the remote address. func (l *TCPListener) AcceptTCP() (c *TCPConn, err error) { - if l == nil || l.fd == nil || l.fd.sysfd < 0 { + if l == nil || l.fd == nil { return nil, syscall.EINVAL } fd, err := l.fd.accept(sockaddrToTCP) @@ -287,3 +276,30 @@ func (l *TCPListener) SetDeadline(t time.Time) error { // It is the caller's responsibility to close f when finished. // Closing l does not affect f, and closing f does not affect l. func (l *TCPListener) File() (f *os.File, err error) { return l.fd.dup() } + +// ListenTCP announces on the TCP address laddr and returns a TCP listener. +// Net must be "tcp", "tcp4", or "tcp6". +// If laddr has a port of 0, it means to listen on some available port. +// The caller can use l.Addr() to retrieve the chosen address. +func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { + switch net { + case "tcp", "tcp4", "tcp6": + default: + return nil, UnknownNetworkError(net) + } + if laddr == nil { + laddr = &TCPAddr{} + } + fd, err := internetSocket(net, laddr.toAddr(), nil, noDeadline, syscall.SOCK_STREAM, 0, "listen", sockaddrToTCP) + if err != nil { + return nil, err + } + err = syscall.Listen(fd.sysfd, listenerBacklog) + if err != nil { + closesocket(fd.sysfd) + return nil, &OpError{"listen", net, laddr, err} + } + l := new(TCPListener) + l.fd = fd + return l, nil +} diff --git a/libgo/go/net/textproto/reader.go b/libgo/go/net/textproto/reader.go index 3777424..855350c 100644 --- a/libgo/go/net/textproto/reader.go +++ b/libgo/go/net/textproto/reader.go @@ -486,6 +486,7 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { // letter and any letter following a hyphen to upper case; // the rest are converted to lowercase. For example, the // canonical key for "accept-encoding" is "Accept-Encoding". +// MIME header keys are assumed to be ASCII only. func CanonicalMIMEHeaderKey(s string) string { // Quick check for canonical encoding. upper := true @@ -502,28 +503,90 @@ func CanonicalMIMEHeaderKey(s string) string { return s } +const toLower = 'a' - 'A' + // canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is // allowed to mutate the provided byte slice before returning the // string. func canonicalMIMEHeaderKey(a []byte) string { - // Canonicalize: first letter upper case - // and upper case after each dash. - // (Host, User-Agent, If-Modified-Since). - // MIME headers are ASCII only, so no Unicode issues. + // Look for it in commonHeaders , so that we can avoid an + // allocation by sharing the strings among all users + // of textproto. If we don't find it, a has been canonicalized + // so just return string(a). upper := true - for i, v := range a { - if v == ' ' { + lo := 0 + hi := len(commonHeaders) + for i := 0; i < len(a); i++ { + // Canonicalize: first letter upper case + // and upper case after each dash. + // (Host, User-Agent, If-Modified-Since). + // MIME headers are ASCII only, so no Unicode issues. + if a[i] == ' ' { a[i] = '-' upper = true continue } - if upper && 'a' <= v && v <= 'z' { - a[i] = v + 'A' - 'a' + c := a[i] + if upper && 'a' <= c && c <= 'z' { + c -= toLower + } else if !upper && 'A' <= c && c <= 'Z' { + c += toLower } - if !upper && 'A' <= v && v <= 'Z' { - a[i] = v + 'a' - 'A' + a[i] = c + upper = c == '-' // for next time + + if lo < hi { + for lo < hi && (len(commonHeaders[lo]) <= i || commonHeaders[lo][i] < c) { + lo++ + } + for hi > lo && commonHeaders[hi-1][i] > c { + hi-- + } } - upper = v == '-' + } + if lo < hi && len(commonHeaders[lo]) == len(a) { + return commonHeaders[lo] } return string(a) } + +var commonHeaders = []string{ + "Accept", + "Accept-Charset", + "Accept-Encoding", + "Accept-Language", + "Accept-Ranges", + "Cache-Control", + "Cc", + "Connection", + "Content-Id", + "Content-Language", + "Content-Length", + "Content-Transfer-Encoding", + "Content-Type", + "Date", + "Dkim-Signature", + "Etag", + "Expires", + "From", + "Host", + "If-Modified-Since", + "If-None-Match", + "In-Reply-To", + "Last-Modified", + "Location", + "Message-Id", + "Mime-Version", + "Pragma", + "Received", + "Return-Path", + "Server", + "Set-Cookie", + "Subject", + "To", + "User-Agent", + "Via", + "X-Forwarded-For", + "X-Imforwards", + "X-Powered-By", +} diff --git a/libgo/go/net/textproto/reader_test.go b/libgo/go/net/textproto/reader_test.go index 9b6c76a..26987f6 100644 --- a/libgo/go/net/textproto/reader_test.go +++ b/libgo/go/net/textproto/reader_test.go @@ -24,6 +24,7 @@ var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{ {"uSER-aGENT", "User-Agent"}, {"user-agent", "User-Agent"}, {"USER-AGENT", "User-Agent"}, + {"üser-agenT", "üser-Agent"}, // non-ASCII unchanged } func TestCanonicalMIMEHeaderKey(t *testing.T) { @@ -241,18 +242,94 @@ func TestRFC959Lines(t *testing.T) { } } +func TestCommonHeaders(t *testing.T) { + // need to disable the commonHeaders-based optimization + // during this check, or we'd not be testing anything + oldch := commonHeaders + commonHeaders = []string{} + defer func() { commonHeaders = oldch }() + + last := "" + for _, h := range oldch { + if last > h { + t.Errorf("%v is out of order", h) + } + if last == h { + t.Errorf("%v is duplicated", h) + } + if canon := CanonicalMIMEHeaderKey(h); h != canon { + t.Errorf("%v is not canonical", h) + } + last = h + } +} + +var clientHeaders = strings.Replace(`Host: golang.org +Connection: keep-alive +Cache-Control: max-age=0 +Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 +User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3 +Accept-Encoding: gzip,deflate,sdch +Accept-Language: en-US,en;q=0.8,fr-CH;q=0.6 +Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 +COOKIE: __utma=000000000.0000000000.0000000000.0000000000.0000000000.00; __utmb=000000000.0.00.0000000000; __utmc=000000000; __utmz=000000000.0000000000.00.0.utmcsr=code.google.com|utmccn=(referral)|utmcmd=referral|utmcct=/p/go/issues/detail +Non-Interned: test + +`, "\n", "\r\n", -1) + +var serverHeaders = strings.Replace(`Content-Type: text/html; charset=utf-8 +Content-Encoding: gzip +Date: Thu, 27 Sep 2012 09:03:33 GMT +Server: Google Frontend +Cache-Control: private +Content-Length: 2298 +VIA: 1.1 proxy.example.com:80 (XXX/n.n.n-nnn) +Connection: Close +Non-Interned: test + +`, "\n", "\r\n", -1) + func BenchmarkReadMIMEHeader(b *testing.B) { var buf bytes.Buffer br := bufio.NewReader(&buf) r := NewReader(br) for i := 0; i < b.N; i++ { - buf.WriteString("User-Agent: not mozilla\r\nContent-Length: 23452\r\nContent-Type: text/html; charset-utf8\r\nFoo-Bar: foobar\r\nfoo-bar: some more string\r\n\r\n") + var want int + var find string + if (i & 1) == 1 { + buf.WriteString(clientHeaders) + want = 10 + find = "Cookie" + } else { + buf.WriteString(serverHeaders) + want = 9 + find = "Via" + } + h, err := r.ReadMIMEHeader() + if err != nil { + b.Fatal(err) + } + if len(h) != want { + b.Fatalf("wrong number of headers: got %d, want %d", len(h), want) + } + if _, ok := h[find]; !ok { + b.Fatalf("did not find key %s", find) + } + } +} + +func BenchmarkUncommon(b *testing.B) { + var buf bytes.Buffer + br := bufio.NewReader(&buf) + r := NewReader(br) + for i := 0; i < b.N; i++ { + buf.WriteString("uncommon-header-for-benchmark: foo\r\n\r\n") h, err := r.ReadMIMEHeader() if err != nil { b.Fatal(err) } - if len(h) != 4 { - b.Fatalf("want 4") + if _, ok := h["Uncommon-Header-For-Benchmark"]; !ok { + b.Fatal("Missing result header.") } } } diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go index 672fb72..68d8ced 100644 --- a/libgo/go/net/timeout_test.go +++ b/libgo/go/net/timeout_test.go @@ -119,3 +119,114 @@ func TestDeadlineReset(t *testing.T) { t.Errorf("unexpected return from Accept; err=%v", err) } } + +func TestTimeoutAccept(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Logf("skipping test on %q", runtime.GOOS) + return + } + ln, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + tl := ln.(*TCPListener) + tl.SetDeadline(time.Now().Add(100 * time.Millisecond)) + errc := make(chan error, 1) + go func() { + _, err := ln.Accept() + errc <- err + }() + select { + case <-time.After(1 * time.Second): + // Accept shouldn't block indefinitely + t.Errorf("Accept didn't return in an expected time") + case <-errc: + // Pass. + } +} + +func TestReadWriteDeadline(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Logf("skipping test on %q", runtime.GOOS) + return + } + + if !canCancelIO { + t.Logf("skipping test on this system") + return + } + const ( + readTimeout = 50 * time.Millisecond + writeTimeout = 250 * time.Millisecond + ) + checkTimeout := func(command string, start time.Time, should time.Duration) { + is := time.Now().Sub(start) + d := is - should + if d < -30*time.Millisecond || !testing.Short() && 150*time.Millisecond < d { + t.Errorf("%s timeout test failed: is=%v should=%v\n", command, is, should) + } + } + + ln, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ListenTCP on :0: %v", err) + } + + lnquit := make(chan bool) + + go func() { + c, err := ln.Accept() + if err != nil { + t.Fatalf("Accept: %v", err) + } + defer c.Close() + lnquit <- true + }() + + c, err := Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer c.Close() + + start := time.Now() + err = c.SetReadDeadline(start.Add(readTimeout)) + if err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + err = c.SetWriteDeadline(start.Add(writeTimeout)) + if err != nil { + t.Fatalf("SetWriteDeadline: %v", err) + } + + quit := make(chan bool) + + go func() { + var buf [10]byte + _, err := c.Read(buf[:]) + if err == nil { + t.Errorf("Read should not succeed") + } + checkTimeout("Read", start, readTimeout) + quit <- true + }() + + go func() { + var buf [10000]byte + for { + _, err := c.Write(buf[:]) + if err != nil { + break + } + } + checkTimeout("Write", start, writeTimeout) + quit <- true + }() + + <-quit + <-quit + <-lnquit +} diff --git a/libgo/go/net/udp_test.go b/libgo/go/net/udp_test.go index f80d3b5..37b904f 100644 --- a/libgo/go/net/udp_test.go +++ b/libgo/go/net/udp_test.go @@ -87,3 +87,33 @@ func testWriteToPacketConn(t *testing.T, raddr string) { t.Fatal("Write should fail") } } + +var udpConnLocalNameTests = []struct { + net string + laddr *UDPAddr +}{ + {"udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)}}, + {"udp4", &UDPAddr{}}, + {"udp4", nil}, +} + +func TestUDPConnLocalName(t *testing.T) { + if testing.Short() || !*testExternal { + t.Logf("skipping test to avoid external network") + return + } + + for _, tt := range udpConnLocalNameTests { + c, err := ListenUDP(tt.net, tt.laddr) + if err != nil { + t.Errorf("ListenUDP failed: %v", err) + return + } + defer c.Close() + la := c.LocalAddr() + if a, ok := la.(*UDPAddr); !ok || a.Port == 0 { + t.Errorf("got %v; expected a proper address with non-zero port number", la) + return + } + } +} diff --git a/libgo/go/net/udpsock.go b/libgo/go/net/udpsock.go index 62b27d9..bf2107b 100644 --- a/libgo/go/net/udpsock.go +++ b/libgo/go/net/udpsock.go @@ -6,7 +6,10 @@ package net -import "errors" +import ( + "errors" + "time" +) var ErrWriteToConnected = errors.New("use of WriteTo with pre-connected UDP") @@ -32,7 +35,11 @@ func (a *UDPAddr) String() string { // "udp4" or "udp6". A literal IPv6 host address must be // enclosed in square brackets, as in "[::]:80". func ResolveUDPAddr(net, addr string) (*UDPAddr, error) { - ip, port, err := hostPortToIP(net, addr) + return resolveUDPAddr(net, addr, noDeadline) +} + +func resolveUDPAddr(net, addr string, deadline time.Time) (*UDPAddr, error) { + ip, port, err := hostPortToIP(net, addr, deadline) if err != nil { return nil, err } diff --git a/libgo/go/net/udpsock_plan9.go b/libgo/go/net/udpsock_plan9.go index aaa7e5b..6a828e1 100644 --- a/libgo/go/net/udpsock_plan9.go +++ b/libgo/go/net/udpsock_plan9.go @@ -10,12 +10,13 @@ import ( "errors" "os" "syscall" + "time" ) // UDPConn is the implementation of the Conn and PacketConn // interfaces for UDP network connections. type UDPConn struct { - plan9Conn + conn } // UDP-specific methods. @@ -31,14 +32,14 @@ func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { if !c.ok() { return 0, nil, syscall.EINVAL } - if c.data == nil { - c.data, err = os.OpenFile(c.dir+"/data", os.O_RDWR, 0) + if c.fd.data == nil { + c.fd.data, err = os.OpenFile(c.fd.dir+"/data", os.O_RDWR, 0) if err != nil { return 0, nil, err } } buf := make([]byte, udpHeaderSize+len(b)) - m, err := c.data.Read(buf) + m, err := c.fd.data.Read(buf) if err != nil { return } @@ -80,23 +81,23 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err error) { if !c.ok() { return 0, syscall.EINVAL } - if c.data == nil { - c.data, err = os.OpenFile(c.dir+"/data", os.O_RDWR, 0) + if c.fd.data == nil { + c.fd.data, err = os.OpenFile(c.fd.dir+"/data", os.O_RDWR, 0) if err != nil { return 0, err } } h := new(udpHeader) h.raddr = addr.IP.To16() - h.laddr = c.laddr.(*UDPAddr).IP.To16() + h.laddr = c.fd.laddr.(*UDPAddr).IP.To16() h.ifcaddr = IPv6zero // ignored (receive only) h.rport = uint16(addr.Port) - h.lport = uint16(c.laddr.(*UDPAddr).Port) + h.lport = uint16(c.fd.laddr.(*UDPAddr).Port) buf := make([]byte, udpHeaderSize+len(b)) i := copy(buf, h.Bytes()) copy(buf[i:], b) - return c.data.Write(buf) + return c.fd.data.Write(buf) } // WriteTo implements the PacketConn WriteTo method. @@ -106,7 +107,7 @@ func (c *UDPConn) WriteTo(b []byte, addr Addr) (n int, err error) { } a, ok := addr.(*UDPAddr) if !ok { - return 0, &OpError{"write", c.dir, addr, syscall.EINVAL} + return 0, &OpError{"write", c.fd.dir, addr, syscall.EINVAL} } return c.WriteToUDP(b, a) } @@ -122,6 +123,13 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er // which must be "udp", "udp4", or "udp6". If laddr is not nil, it is // used as the local address for the connection. func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error) { + return dialUDP(net, laddr, raddr, noDeadline) +} + +func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) { + if !deadline.IsZero() { + panic("net.dialUDP: deadline not implemented on Plan 9") + } switch net { case "udp", "udp4", "udp6": default: @@ -130,11 +138,11 @@ func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error) { if raddr == nil { return nil, &OpError{"dial", net, nil, errMissingAddress} } - c1, err := dialPlan9(net, laddr, raddr) + fd, err := dialPlan9(net, laddr, raddr) if err != nil { - return + return nil, err } - return &UDPConn{*c1}, nil + return &UDPConn{conn{fd}}, nil } const udpHeaderSize = 16*3 + 2*2 @@ -169,24 +177,24 @@ func unmarshalUDPHeader(b []byte) (*udpHeader, []byte) { // address laddr. The returned connection c's ReadFrom and WriteTo // methods can be used to receive and send UDP packets with per-packet // addressing. -func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err error) { +func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) { switch net { case "udp", "udp4", "udp6": default: return nil, UnknownNetworkError(net) } if laddr == nil { - return nil, &OpError{"listen", net, nil, errMissingAddress} + laddr = &UDPAddr{} } l, err := listenPlan9(net, laddr) if err != nil { - return + return nil, err } _, err = l.ctl.WriteString("headers") if err != nil { - return + return nil, err } - return &UDPConn{*l.plan9Conn()}, nil + return &UDPConn{conn{l.netFD()}}, nil } // ListenMulticastUDP listens for incoming multicast UDP packets diff --git a/libgo/go/net/udpsock_posix.go b/libgo/go/net/udpsock_posix.go index e075380..d7329bf 100644 --- a/libgo/go/net/udpsock_posix.go +++ b/libgo/go/net/udpsock_posix.go @@ -8,7 +8,10 @@ package net -import "syscall" +import ( + "syscall" + "time" +) func sockaddrToUDP(sa syscall.Sockaddr) Addr { switch sa := sa.(type) { @@ -160,6 +163,10 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er // which must be "udp", "udp4", or "udp6". If laddr is not nil, it is used // as the local address for the connection. func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) { + return dialUDP(net, laddr, raddr, noDeadline) +} + +func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) { switch net { case "udp", "udp4", "udp6": default: @@ -168,7 +175,7 @@ func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) { if raddr == nil { return nil, &OpError{"dial", net, nil, errMissingAddress} } - fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_DGRAM, 0, "dial", sockaddrToUDP) + fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), deadline, syscall.SOCK_DGRAM, 0, "dial", sockaddrToUDP) if err != nil { return nil, err } @@ -186,9 +193,9 @@ func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) { return nil, UnknownNetworkError(net) } if laddr == nil { - return nil, &OpError{"listen", net, nil, errMissingAddress} + laddr = &UDPAddr{} } - fd, err := internetSocket(net, laddr.toAddr(), nil, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP) + fd, err := internetSocket(net, laddr.toAddr(), nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP) if err != nil { return nil, err } @@ -208,7 +215,7 @@ func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, e if gaddr == nil || gaddr.IP == nil { return nil, &OpError{"listenmulticast", net, nil, errMissingAddress} } - fd, err := internetSocket(net, gaddr.toAddr(), nil, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP) + fd, err := internetSocket(net, gaddr.toAddr(), nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP) if err != nil { return nil, err } diff --git a/libgo/go/net/unicast_posix_test.go b/libgo/go/net/unicast_posix_test.go index 5b39e25..e1d4b0d 100644 --- a/libgo/go/net/unicast_posix_test.go +++ b/libgo/go/net/unicast_posix_test.go @@ -171,9 +171,9 @@ var dualStackListenerTests = []struct { // Test cases and expected results for the attemping 2nd listen on the same port // 1st listen 2nd listen darwin freebsd linux openbsd // ------------------------------------------------------------------------------------ - // "tcp" "" "tcp" "" - - - - - // "tcp" "" "tcp" "0.0.0.0" - - - - - // "tcp" "0.0.0.0" "tcp" "" - - - - + // "tcp" "" "tcp" "" - - - - + // "tcp" "" "tcp" "0.0.0.0" - - - - + // "tcp" "0.0.0.0" "tcp" "" - - - - // ------------------------------------------------------------------------------------ // "tcp" "" "tcp" "[::]" - - - ok // "tcp" "[::]" "tcp" "" - - - ok diff --git a/libgo/go/net/unixsock_plan9.go b/libgo/go/net/unixsock_plan9.go index 2140375..342e26f 100644 --- a/libgo/go/net/unixsock_plan9.go +++ b/libgo/go/net/unixsock_plan9.go @@ -139,6 +139,10 @@ func (c *UnixConn) CloseWrite() error { // which must be "unix" or "unixgram". If laddr is not nil, it is // used as the local address for the connection. func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { + return dialUnix(net, laddr, raddr, noDeadline) +} + +func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) { return nil, syscall.EPLAN9 } diff --git a/libgo/go/net/unixsock_posix.go b/libgo/go/net/unixsock_posix.go index 2bef5ea..16ebd58 100644 --- a/libgo/go/net/unixsock_posix.go +++ b/libgo/go/net/unixsock_posix.go @@ -14,7 +14,7 @@ import ( "time" ) -func unixSocket(net string, laddr, raddr *UnixAddr, mode string) (fd *netFD, err error) { +func unixSocket(net string, laddr, raddr *UnixAddr, mode string, deadline time.Time) (fd *netFD, err error) { var sotype int switch net { default: @@ -59,7 +59,7 @@ func unixSocket(net string, laddr, raddr *UnixAddr, mode string) (fd *netFD, err f = sockaddrToUnixpacket } - fd, err = socket(net, syscall.AF_UNIX, sotype, 0, false, la, ra, f) + fd, err = socket(net, syscall.AF_UNIX, sotype, 0, false, la, ra, deadline, f) if err != nil { goto Error } @@ -229,7 +229,11 @@ func (c *UnixConn) CloseWrite() error { // which must be "unix" or "unixgram". If laddr is not nil, it is used // as the local address for the connection. func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { - fd, err := unixSocket(net, laddr, raddr, "dial") + return dialUnix(net, laddr, raddr, noDeadline) +} + +func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) { + fd, err := unixSocket(net, laddr, raddr, "dial", deadline) if err != nil { return nil, err } @@ -253,7 +257,7 @@ func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) { if laddr != nil { laddr = &UnixAddr{laddr.Name, net} // make our own copy } - fd, err := unixSocket(net, laddr, nil, "listen") + fd, err := unixSocket(net, laddr, nil, "listen", noDeadline) if err != nil { return nil, err } @@ -309,9 +313,7 @@ func (l *UnixListener) Close() error { if l.path[0] != '@' { syscall.Unlink(l.path) } - err := l.fd.Close() - l.fd = nil - return err + return l.fd.Close() } // Addr returns the listener's network address. @@ -344,7 +346,7 @@ func ListenUnixgram(net string, laddr *UnixAddr) (*UDPConn, error) { if laddr == nil { return nil, &OpError{"listen", net, nil, errMissingAddress} } - fd, err := unixSocket(net, laddr, nil, "listen") + fd, err := unixSocket(net, laddr, nil, "listen", noDeadline) if err != nil { return nil, err } -- cgit v1.1