diff options
Diffstat (limited to 'libgo/go/net')
208 files changed, 15969 insertions, 6140 deletions
diff --git a/libgo/go/net/addrselect.go b/libgo/go/net/addrselect.go new file mode 100644 index 00000000000..e22fbac5cee --- /dev/null +++ b/libgo/go/net/addrselect.go @@ -0,0 +1,388 @@ +// Copyright 2015 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 dragonfly freebsd linux netbsd openbsd solaris + +// Minimal RFC 6724 address selection. + +package net + +import "sort" + +func sortByRFC6724(addrs []IPAddr) { + if len(addrs) < 2 { + return + } + sortByRFC6724withSrcs(addrs, srcAddrs(addrs)) +} + +func sortByRFC6724withSrcs(addrs []IPAddr, srcs []IP) { + if len(addrs) != len(srcs) { + panic("internal error") + } + addrAttr := make([]ipAttr, len(addrs)) + srcAttr := make([]ipAttr, len(srcs)) + for i, v := range addrs { + addrAttr[i] = ipAttrOf(v.IP) + srcAttr[i] = ipAttrOf(srcs[i]) + } + sort.Stable(&byRFC6724{ + addrs: addrs, + addrAttr: addrAttr, + srcs: srcs, + srcAttr: srcAttr, + }) +} + +// srcsAddrs tries to UDP-connect to each address to see if it has a +// route. (This doesn't send any packets). The destination port +// number is irrelevant. +func srcAddrs(addrs []IPAddr) []IP { + srcs := make([]IP, len(addrs)) + dst := UDPAddr{Port: 9} + for i := range addrs { + dst.IP = addrs[i].IP + dst.Zone = addrs[i].Zone + c, err := DialUDP("udp", nil, &dst) + if err == nil { + if src, ok := c.LocalAddr().(*UDPAddr); ok { + srcs[i] = src.IP + } + c.Close() + } + } + return srcs +} + +type ipAttr struct { + Scope scope + Precedence uint8 + Label uint8 +} + +func ipAttrOf(ip IP) ipAttr { + if ip == nil { + return ipAttr{} + } + match := rfc6724policyTable.Classify(ip) + return ipAttr{ + Scope: classifyScope(ip), + Precedence: match.Precedence, + Label: match.Label, + } +} + +type byRFC6724 struct { + addrs []IPAddr // addrs to sort + addrAttr []ipAttr + srcs []IP // or nil if unreachable + srcAttr []ipAttr +} + +func (s *byRFC6724) Len() int { return len(s.addrs) } + +func (s *byRFC6724) Swap(i, j int) { + s.addrs[i], s.addrs[j] = s.addrs[j], s.addrs[i] + s.srcs[i], s.srcs[j] = s.srcs[j], s.srcs[i] + s.addrAttr[i], s.addrAttr[j] = s.addrAttr[j], s.addrAttr[i] + s.srcAttr[i], s.srcAttr[j] = s.srcAttr[j], s.srcAttr[i] +} + +// Less reports whether i is a better destination address for this +// host than j. +// +// The algorithm and variable names comes from RFC 6724 section 6. +func (s *byRFC6724) Less(i, j int) bool { + DA := s.addrs[i].IP + DB := s.addrs[j].IP + SourceDA := s.srcs[i] + SourceDB := s.srcs[j] + attrDA := &s.addrAttr[i] + attrDB := &s.addrAttr[j] + attrSourceDA := &s.srcAttr[i] + attrSourceDB := &s.srcAttr[j] + + const preferDA = true + const preferDB = false + + // Rule 1: Avoid unusable destinations. + // If DB is known to be unreachable or if Source(DB) is undefined, then + // prefer DA. Similarly, if DA is known to be unreachable or if + // Source(DA) is undefined, then prefer DB. + if SourceDA == nil && SourceDB == nil { + return false // "equal" + } + if SourceDB == nil { + return preferDA + } + if SourceDA == nil { + return preferDB + } + + // Rule 2: Prefer matching scope. + // If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), + // then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and + // Scope(DB) = Scope(Source(DB)), then prefer DB. + if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope { + return preferDA + } + if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope { + return preferDB + } + + // Rule 3: Avoid deprecated addresses. + // If Source(DA) is deprecated and Source(DB) is not, then prefer DB. + // Similarly, if Source(DA) is not deprecated and Source(DB) is + // deprecated, then prefer DA. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 4: Prefer home addresses. + // If Source(DA) is simultaneously a home address and care-of address + // and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is + // simultaneously a home address and care-of address and Source(DA) is + // not, then prefer DB. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 5: Prefer matching label. + // If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), + // then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and + // Label(Source(DB)) = Label(DB), then prefer DB. + if attrSourceDA.Label == attrDA.Label && + attrSourceDB.Label != attrDB.Label { + return preferDA + } + if attrSourceDA.Label != attrDA.Label && + attrSourceDB.Label == attrDB.Label { + return preferDB + } + + // Rule 6: Prefer higher precedence. + // If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if + // Precedence(DA) < Precedence(DB), then prefer DB. + if attrDA.Precedence > attrDB.Precedence { + return preferDA + } + if attrDA.Precedence < attrDB.Precedence { + return preferDB + } + + // Rule 7: Prefer native transport. + // If DA is reached via an encapsulating transition mechanism (e.g., + // IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is + // reached via encapsulation and DA is not, then prefer DA. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 8: Prefer smaller scope. + // If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > + // Scope(DB), then prefer DB. + if attrDA.Scope < attrDB.Scope { + return preferDA + } + if attrDA.Scope > attrDB.Scope { + return preferDB + } + + // Rule 9: Use longest matching prefix. + // When DA and DB belong to the same address family (both are IPv6 or + // both are IPv4): If CommonPrefixLen(Source(DA), DA) > + // CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if + // CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), + // then prefer DB. + da4 := DA.To4() != nil + db4 := DB.To4() != nil + if da4 == db4 { + commonA := commonPrefixLen(SourceDA, DA) + commonB := commonPrefixLen(SourceDB, DB) + if commonA > commonB { + return preferDA + } + if commonA < commonB { + return preferDB + } + } + + // Rule 10: Otherwise, leave the order unchanged. + // If DA preceded DB in the original list, prefer DA. + // Otherwise, prefer DB. + return false // "equal" +} + +type policyTableEntry struct { + Prefix *IPNet + Precedence uint8 + Label uint8 +} + +type policyTable []policyTableEntry + +// RFC 6724 section 2.1. +var rfc6724policyTable = policyTable{ + { + Prefix: mustCIDR("::1/128"), + Precedence: 50, + Label: 0, + }, + { + Prefix: mustCIDR("::/0"), + Precedence: 40, + Label: 1, + }, + { + // IPv4-compatible, etc. + Prefix: mustCIDR("::ffff:0:0/96"), + Precedence: 35, + Label: 4, + }, + { + // 6to4 + Prefix: mustCIDR("2002::/16"), + Precedence: 30, + Label: 2, + }, + { + // Teredo + Prefix: mustCIDR("2001::/32"), + Precedence: 5, + Label: 5, + }, + { + Prefix: mustCIDR("fc00::/7"), + Precedence: 3, + Label: 13, + }, + { + Prefix: mustCIDR("::/96"), + Precedence: 1, + Label: 3, + }, + { + Prefix: mustCIDR("fec0::/10"), + Precedence: 1, + Label: 11, + }, + { + Prefix: mustCIDR("3ffe::/16"), + Precedence: 1, + Label: 12, + }, +} + +func init() { + sort.Sort(sort.Reverse(byMaskLength(rfc6724policyTable))) +} + +// byMaskLength sorts policyTableEntry by the size of their Prefix.Mask.Size, +// from smallest mask, to largest. +type byMaskLength []policyTableEntry + +func (s byMaskLength) Len() int { return len(s) } +func (s byMaskLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byMaskLength) Less(i, j int) bool { + isize, _ := s[i].Prefix.Mask.Size() + jsize, _ := s[j].Prefix.Mask.Size() + return isize < jsize +} + +// mustCIDR calls ParseCIDR and panics on any error, or if the network +// is not IPv6. +func mustCIDR(s string) *IPNet { + ip, ipNet, err := ParseCIDR(s) + if err != nil { + panic(err.Error()) + } + if len(ip) != IPv6len { + panic("unexpected IP length") + } + return ipNet +} + +// Classify returns the policyTableEntry of the entry with the longest +// matching prefix that contains ip. +// The table t must be sorted from largest mask size to smallest. +func (t policyTable) Classify(ip IP) policyTableEntry { + for _, ent := range t { + if ent.Prefix.Contains(ip) { + return ent + } + } + return policyTableEntry{} +} + +// RFC 6724 section 3.1. +type scope uint8 + +const ( + scopeInterfaceLocal scope = 0x1 + scopeLinkLocal scope = 0x2 + scopeAdminLocal scope = 0x4 + scopeSiteLocal scope = 0x5 + scopeOrgLocal scope = 0x8 + scopeGlobal scope = 0xe +) + +func classifyScope(ip IP) scope { + if ip.IsLoopback() || ip.IsLinkLocalUnicast() { + return scopeLinkLocal + } + ipv6 := len(ip) == IPv6len && ip.To4() == nil + if ipv6 && ip.IsMulticast() { + return scope(ip[1] & 0xf) + } + // Site-local addresses are defined in RFC 3513 section 2.5.6 + // (and deprecated in RFC 3879). + if ipv6 && ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 { + return scopeSiteLocal + } + return scopeGlobal +} + +// commonPrefixLen reports the length of the longest prefix (looking +// at the most significant, or leftmost, bits) that the +// two addresses have in common, up to the length of a's prefix (i.e., +// the portion of the address not including the interface ID). +// +// If a or b is an IPv4 address as an IPv6 address, the IPv4 addresses +// are compared (with max common prefix length of 32). +// If a and b are different IP versions, 0 is returned. +// +// See https://tools.ietf.org/html/rfc6724#section-2.2 +func commonPrefixLen(a, b IP) (cpl int) { + if a4 := a.To4(); a4 != nil { + a = a4 + } + if b4 := b.To4(); b4 != nil { + b = b4 + } + if len(a) != len(b) { + return 0 + } + // If IPv6, only up to the prefix (first 64 bits) + if len(a) > 8 { + a = a[:8] + b = b[:8] + } + for len(a) > 0 { + if a[0] == b[0] { + cpl += 8 + a = a[1:] + b = b[1:] + continue + } + bits := 8 + ab, bb := a[0], b[0] + for { + ab >>= 1 + bb >>= 1 + bits-- + if ab == bb { + cpl += bits + return + } + } + } + return +} diff --git a/libgo/go/net/addrselect_test.go b/libgo/go/net/addrselect_test.go new file mode 100644 index 00000000000..562022772fa --- /dev/null +++ b/libgo/go/net/addrselect_test.go @@ -0,0 +1,219 @@ +// Copyright 2015 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 dragonfly freebsd linux netbsd openbsd solaris + +package net + +import ( + "reflect" + "testing" +) + +func TestSortByRFC6724(t *testing.T) { + tests := []struct { + in []IPAddr + srcs []IP + want []IPAddr + reverse bool // also test it starting backwards + }{ + // Examples from RFC 6724 section 10.2: + + // Prefer matching scope. + { + in: []IPAddr{ + {IP: ParseIP("2001:db8:1::1")}, + {IP: ParseIP("198.51.100.121")}, + }, + srcs: []IP{ + ParseIP("2001:db8:1::2"), + ParseIP("169.254.13.78"), + }, + want: []IPAddr{ + {IP: ParseIP("2001:db8:1::1")}, + {IP: ParseIP("198.51.100.121")}, + }, + reverse: true, + }, + + // Prefer matching scope. + { + in: []IPAddr{ + {IP: ParseIP("2001:db8:1::1")}, + {IP: ParseIP("198.51.100.121")}, + }, + srcs: []IP{ + ParseIP("fe80::1"), + ParseIP("198.51.100.117"), + }, + want: []IPAddr{ + {IP: ParseIP("198.51.100.121")}, + {IP: ParseIP("2001:db8:1::1")}, + }, + reverse: true, + }, + + // Prefer higher precedence. + { + in: []IPAddr{ + {IP: ParseIP("2001:db8:1::1")}, + {IP: ParseIP("10.1.2.3")}, + }, + srcs: []IP{ + ParseIP("2001:db8:1::2"), + ParseIP("10.1.2.4"), + }, + want: []IPAddr{ + {IP: ParseIP("2001:db8:1::1")}, + {IP: ParseIP("10.1.2.3")}, + }, + reverse: true, + }, + + // Prefer smaller scope. + { + in: []IPAddr{ + {IP: ParseIP("2001:db8:1::1")}, + {IP: ParseIP("fe80::1")}, + }, + srcs: []IP{ + ParseIP("2001:db8:1::2"), + ParseIP("fe80::2"), + }, + want: []IPAddr{ + {IP: ParseIP("fe80::1")}, + {IP: ParseIP("2001:db8:1::1")}, + }, + reverse: true, + }, + } + for i, tt := range tests { + inCopy := make([]IPAddr, len(tt.in)) + copy(inCopy, tt.in) + srcCopy := make([]IP, len(tt.in)) + copy(srcCopy, tt.srcs) + sortByRFC6724withSrcs(inCopy, srcCopy) + if !reflect.DeepEqual(inCopy, tt.want) { + t.Errorf("test %d:\nin = %s\ngot: %s\nwant: %s\n", i, tt.in, inCopy, tt.want) + } + if tt.reverse { + copy(inCopy, tt.in) + copy(srcCopy, tt.srcs) + for j := 0; j < len(inCopy)/2; j++ { + k := len(inCopy) - j - 1 + inCopy[j], inCopy[k] = inCopy[k], inCopy[j] + srcCopy[j], srcCopy[k] = srcCopy[k], srcCopy[j] + } + sortByRFC6724withSrcs(inCopy, srcCopy) + if !reflect.DeepEqual(inCopy, tt.want) { + t.Errorf("test %d, starting backwards:\nin = %s\ngot: %s\nwant: %s\n", i, tt.in, inCopy, tt.want) + } + } + + } + +} + +func TestRFC6724PolicyTableClassify(t *testing.T) { + tests := []struct { + ip IP + want policyTableEntry + }{ + { + ip: ParseIP("127.0.0.1"), + want: policyTableEntry{ + Prefix: &IPNet{IP: ParseIP("::ffff:0:0"), Mask: CIDRMask(96, 128)}, + Precedence: 35, + Label: 4, + }, + }, + { + ip: ParseIP("2601:645:8002:a500:986f:1db8:c836:bd65"), + want: policyTableEntry{ + Prefix: &IPNet{IP: ParseIP("::"), Mask: CIDRMask(0, 128)}, + Precedence: 40, + Label: 1, + }, + }, + { + ip: ParseIP("::1"), + want: policyTableEntry{ + Prefix: &IPNet{IP: ParseIP("::1"), Mask: CIDRMask(128, 128)}, + Precedence: 50, + Label: 0, + }, + }, + { + ip: ParseIP("2002::ab12"), + want: policyTableEntry{ + Prefix: &IPNet{IP: ParseIP("2002::"), Mask: CIDRMask(16, 128)}, + Precedence: 30, + Label: 2, + }, + }, + } + for i, tt := range tests { + got := rfc6724policyTable.Classify(tt.ip) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%d. Classify(%s) = %v; want %v", i, tt.ip, got, tt.want) + } + } +} + +func TestRFC6724ClassifyScope(t *testing.T) { + tests := []struct { + ip IP + want scope + }{ + {ParseIP("127.0.0.1"), scopeLinkLocal}, // rfc6724#section-3.2 + {ParseIP("::1"), scopeLinkLocal}, // rfc4007#section-4 + {ParseIP("169.254.1.2"), scopeLinkLocal}, // rfc6724#section-3.2 + {ParseIP("fec0::1"), scopeSiteLocal}, + {ParseIP("8.8.8.8"), scopeGlobal}, + + {ParseIP("ff02::"), scopeLinkLocal}, // IPv6 multicast + {ParseIP("ff05::"), scopeSiteLocal}, // IPv6 multicast + {ParseIP("ff04::"), scopeAdminLocal}, // IPv6 multicast + {ParseIP("ff0e::"), scopeGlobal}, // IPv6 multicast + + {IPv4(0xe0, 0, 0, 0), scopeGlobal}, // IPv4 link-local multicast as 16 bytes + {IPv4(0xe0, 2, 2, 2), scopeGlobal}, // IPv4 global multicast as 16 bytes + {IPv4(0xe0, 0, 0, 0).To4(), scopeGlobal}, // IPv4 link-local multicast as 4 bytes + {IPv4(0xe0, 2, 2, 2).To4(), scopeGlobal}, // IPv4 global multicast as 4 bytes + } + for i, tt := range tests { + got := classifyScope(tt.ip) + if got != tt.want { + t.Errorf("%d. classifyScope(%s) = %x; want %x", i, tt.ip, got, tt.want) + } + } +} + +func TestRFC6724CommonPrefixLength(t *testing.T) { + tests := []struct { + a, b IP + want int + }{ + {ParseIP("fe80::1"), ParseIP("fe80::2"), 64}, + {ParseIP("fe81::1"), ParseIP("fe80::2"), 15}, + {ParseIP("127.0.0.1"), ParseIP("fe80::1"), 0}, // diff size + {IPv4(1, 2, 3, 4), IP{1, 2, 3, 4}, 32}, + {IP{1, 2, 255, 255}, IP{1, 2, 0, 0}, 16}, + {IP{1, 2, 127, 255}, IP{1, 2, 0, 0}, 17}, + {IP{1, 2, 63, 255}, IP{1, 2, 0, 0}, 18}, + {IP{1, 2, 31, 255}, IP{1, 2, 0, 0}, 19}, + {IP{1, 2, 15, 255}, IP{1, 2, 0, 0}, 20}, + {IP{1, 2, 7, 255}, IP{1, 2, 0, 0}, 21}, + {IP{1, 2, 3, 255}, IP{1, 2, 0, 0}, 22}, + {IP{1, 2, 1, 255}, IP{1, 2, 0, 0}, 23}, + {IP{1, 2, 0, 255}, IP{1, 2, 0, 0}, 24}, + } + for i, tt := range tests { + got := commonPrefixLen(tt.a, tt.b) + if got != tt.want { + t.Errorf("%d. commonPrefixLen(%s, %s) = %d; want %d", i, tt.a, tt.b, got, tt.want) + } + } + +} diff --git a/libgo/go/net/cgo_android.go b/libgo/go/net/cgo_android.go index 3819ce56a4f..fe9925b840a 100644 --- a/libgo/go/net/cgo_android.go +++ b/libgo/go/net/cgo_android.go @@ -9,6 +9,4 @@ package net //#include <netdb.h> import "C" -func cgoAddrInfoFlags() C.int { - return C.AI_CANONNAME -} +const cgoAddrInfoFlags = C.AI_CANONNAME diff --git a/libgo/go/net/cgo_bsd.go b/libgo/go/net/cgo_bsd.go index ce46f2e8c3a..ae1054b39ad 100644 --- a/libgo/go/net/cgo_bsd.go +++ b/libgo/go/net/cgo_bsd.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !netgo -// +build darwin dragonfly freebsd solaris +// +build cgo,!netgo +// +build darwin dragonfly freebsd package net @@ -13,6 +13,4 @@ package net import "syscall" -func cgoAddrInfoFlags() int { - return (syscall.AI_CANONNAME | syscall.AI_V4MAPPED | syscall.AI_ALL) & syscall.AI_MASK -} +const cgoAddrInfoFlags = (syscall.AI_CANONNAME | syscall.AI_V4MAPPED | syscall.AI_ALL) & syscall.AI_MASK diff --git a/libgo/go/net/cgo_linux.go b/libgo/go/net/cgo_linux.go index 0e332261acc..baf207257fa 100644 --- a/libgo/go/net/cgo_linux.go +++ b/libgo/go/net/cgo_linux.go @@ -12,12 +12,10 @@ package net import "syscall" -func cgoAddrInfoFlags() int { - // NOTE(rsc): In theory there are approximately balanced - // arguments for and against including AI_ADDRCONFIG - // in the flags (it includes IPv4 results only on IPv4 systems, - // and similarly for IPv6), but in practice setting it causes - // getaddrinfo to return the wrong canonical name on Linux. - // So definitely leave it out. - return syscall.AI_CANONNAME | syscall.AI_V4MAPPED | syscall.AI_ALL -} +// NOTE(rsc): In theory there are approximately balanced +// arguments for and against including AI_ADDRCONFIG +// in the flags (it includes IPv4 results only on IPv4 systems, +// and similarly for IPv6), but in practice setting it causes +// getaddrinfo to return the wrong canonical name on Linux. +// So definitely leave it out. +const cgoAddrInfoFlags = syscall.AI_CANONNAME | syscall.AI_V4MAPPED | syscall.AI_ALL diff --git a/libgo/go/net/cgo_netbsd.go b/libgo/go/net/cgo_netbsd.go index 3c13103831f..8a16871906d 100644 --- a/libgo/go/net/cgo_netbsd.go +++ b/libgo/go/net/cgo_netbsd.go @@ -9,8 +9,6 @@ package net /* #include <netdb.h> */ -import "C" +import "syscall" -func cgoAddrInfoFlags() int { - return C.AI_CANONNAME -} +const cgoAddrInfoFlags = syscall.AI_CANONNAME diff --git a/libgo/go/net/cgo_openbsd.go b/libgo/go/net/cgo_openbsd.go index 09c5ad2d9fd..183091366cb 100644 --- a/libgo/go/net/cgo_openbsd.go +++ b/libgo/go/net/cgo_openbsd.go @@ -11,6 +11,4 @@ package net */ import "C" -func cgoAddrInfoFlags() C.int { - return C.AI_CANONNAME -} +const cgoAddrInfoFlags = C.AI_CANONNAME diff --git a/libgo/go/net/cgo_resnew.go b/libgo/go/net/cgo_resnew.go new file mode 100644 index 00000000000..ebca1bda5a8 --- /dev/null +++ b/libgo/go/net/cgo_resnew.go @@ -0,0 +1,36 @@ +// Copyright 2015 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 cgo,!netgo +// +build darwin linux,!android netbsd solaris + +package net + +/* +#include <sys/types.h> +#include <sys/socket.h> + +#include <netdb.h> +*/ + +import ( + "syscall" +) + +//extern getnameinfo +func libc_getnameinfo(*syscall.RawSockaddr, syscall.Socklen_t, *byte, syscall.Size_t, *byte, syscall.Size_t, int) int + +func cgoNameinfoPTR(b []byte, sa *syscall.RawSockaddr, salen syscall.Socklen_t) (int, error) { + syscall.Entersyscall() + gerrno := libc_getnameinfo(sa, salen, &b[0], syscall.Size_t(len(b)), nil, 0, syscall.NI_NAMEREQD) + syscall.Exitsyscall() + var err error + if gerrno == syscall.EAI_SYSTEM { + errno := syscall.GetErrno() + if errno != 0 { + err = errno + } + } + return gerrno, err +} diff --git a/libgo/go/net/cgo_resold.go b/libgo/go/net/cgo_resold.go new file mode 100644 index 00000000000..8e13e4156d0 --- /dev/null +++ b/libgo/go/net/cgo_resold.go @@ -0,0 +1,36 @@ +// Copyright 2015 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 cgo,!netgo +// +build android freebsd dragonfly openbsd + +package net + +/* +#include <sys/types.h> +#include <sys/socket.h> + +#include <netdb.h> +*/ + +import ( + "syscall" +) + +//extern getnameinfo +func libc_getnameinfo(*syscall.RawSockaddr, syscall.Socklen_t, *byte, syscall.Size_t, *byte, syscall.Size_t, int) int + +func cgoNameinfoPTR(b []byte, sa *syscall.RawSockaddr, salen syscall.Socklen_t) (int, error) { + syscall.Entersyscall() + gerrno := libc_getnameinfo(sa, salen, &b[0], syscall.Size(len(b)), nil, 0, syscall.NI_NAMEREQD) + syscall.Exitsyscall() + var err error + if gerrno == syscall.EAI_SYSTEM { + errno := syscall.GetErrno() + if errno != 0 { + err = errno + } + } + return gerrno, err +} diff --git a/libgo/go/net/cgo_socknew.go b/libgo/go/net/cgo_socknew.go new file mode 100644 index 00000000000..81816c644f8 --- /dev/null +++ b/libgo/go/net/cgo_socknew.go @@ -0,0 +1,32 @@ +// Copyright 2015 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 cgo,!netgo +// +build android linux solaris + +package net + +/* +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +*/ + +import ( + "syscall" + "unsafe" +) + +func cgoSockaddrInet4(ip IP) *syscall.RawSockaddr { + sa := syscall.RawSockaddrInet4{Family: syscall.AF_INET} + copy(sa.Addr[:], ip) + return (*syscall.RawSockaddr)(unsafe.Pointer(&sa)) +} + +func cgoSockaddrInet6(ip IP) *syscall.RawSockaddr { + sa := syscall.RawSockaddrInet6{Family: syscall.AF_INET6} + copy(sa.Addr[:], ip) + return (*syscall.RawSockaddr)(unsafe.Pointer(&sa)) +} diff --git a/libgo/go/net/cgo_sockold.go b/libgo/go/net/cgo_sockold.go new file mode 100644 index 00000000000..e80e03b2b1e --- /dev/null +++ b/libgo/go/net/cgo_sockold.go @@ -0,0 +1,32 @@ +// Copyright 2015 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 cgo,!netgo +// +build darwin dragonfly freebsd netbsd openbsd + +package net + +/* +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +*/ + +import ( + "syscall" + "unsafe" +) + +func cgoSockaddrInet4(ip IP) *syscall.RawSockaddr { + sa := syscall.RawSockaddrInet4{Len: syscall.SizeofSockaddrInet4, Family: syscall.AF_INET} + copy(sa.Addr[:], ip) + return (*syscall.RawSockaddr)(unsafe.Pointer(&sa)) +} + +func cgoSockaddrInet6(ip IP) *syscall.RawSockaddr { + sa := syscall.RawSockaddrInet6{Len: syscall.SizeofSockaddrInet6, Family: syscall.AF_INET6} + copy(sa.Addr[:], ip) + return (*syscall.RawSockaddr)(unsafe.Pointer(&sa)) +} diff --git a/libgo/go/net/cgo_solaris.go b/libgo/go/net/cgo_solaris.go new file mode 100644 index 00000000000..c5a7a3549dd --- /dev/null +++ b/libgo/go/net/cgo_solaris.go @@ -0,0 +1,16 @@ +// Copyright 2015 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 cgo,!netgo + +package net + +/* +#cgo LDFLAGS: -lsocket -lnsl +#include <netdb.h> +*/ + +import "syscall" + +const cgoAddrInfoFlags = syscall.AI_CANONNAME | syscall.AI_V4MAPPED | syscall.AI_ALL diff --git a/libgo/go/net/cgo_stub.go b/libgo/go/net/cgo_stub.go index f533c14212f..b86ff7daf19 100644 --- a/libgo/go/net/cgo_stub.go +++ b/libgo/go/net/cgo_stub.go @@ -4,10 +4,16 @@ // +build !cgo netgo -// Stub cgo routines for systems that do not use cgo to do network lookups. - package net +func init() { netGo = true } + +type addrinfoErrno int + +func (eai addrinfoErrno) Error() string { return "<nil>" } +func (eai addrinfoErrno) Temporary() bool { return false } +func (eai addrinfoErrno) Timeout() bool { return false } + func cgoLookupHost(name string) (addrs []string, err error, completed bool) { return nil, nil, false } @@ -16,10 +22,14 @@ func cgoLookupPort(network, service string) (port int, err error, completed bool return 0, nil, false } -func cgoLookupIP(name string) (addrs []IP, err error, completed bool) { +func cgoLookupIP(name string) (addrs []IPAddr, err error, completed bool) { return nil, nil, false } func cgoLookupCNAME(name string) (cname string, err error, completed bool) { return "", nil, false } + +func cgoLookupPTR(addr string) (ptrs []string, err error, completed bool) { + return nil, nil, false +} diff --git a/libgo/go/net/cgo_unix.go b/libgo/go/net/cgo_unix.go index 0abf43410e1..8eafa8cbd44 100644 --- a/libgo/go/net/cgo_unix.go +++ b/libgo/go/net/cgo_unix.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !netgo -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build cgo,!netgo +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net @@ -42,24 +42,30 @@ func bytePtrToString(p *byte) string { return string(a[:i]) } -func cgoLookupHost(name string) (addrs []string, err error, completed bool) { - ip, err, completed := cgoLookupIP(name) - for _, p := range ip { - addrs = append(addrs, p.String()) +// An addrinfoErrno represents a getaddrinfo, getnameinfo-specific +// error number. It's a signed number and a zero value is a non-error +// by convention. +type addrinfoErrno int + +func (eai addrinfoErrno) Error() string { return bytePtrToString(libc_gai_strerror(int(eai))) } +func (eai addrinfoErrno) Temporary() bool { return eai == syscall.EAI_AGAIN } +func (eai addrinfoErrno) Timeout() bool { return false } + +func cgoLookupHost(name string) (hosts []string, err error, completed bool) { + addrs, err, completed := cgoLookupIP(name) + for _, addr := range addrs { + hosts = append(hosts, addr.String()) } return } -func cgoLookupPort(net, service string) (port int, err error, completed bool) { +func cgoLookupPort(network, service string) (port int, err error, completed bool) { acquireThread() defer releaseThread() - var res *syscall.Addrinfo var hints syscall.Addrinfo - - switch net { - case "": - // no hints + switch network { + case "": // no hints case "tcp", "tcp4", "tcp6": hints.Ai_socktype = syscall.SOCK_STREAM hints.Ai_protocol = syscall.IPPROTO_TCP @@ -67,10 +73,10 @@ func cgoLookupPort(net, service string) (port int, err error, completed bool) { hints.Ai_socktype = syscall.SOCK_DGRAM hints.Ai_protocol = syscall.IPPROTO_UDP default: - return 0, UnknownNetworkError(net), true + return 0, &DNSError{Err: "unknown network", Name: network + "/" + service}, true } - if len(net) >= 4 { - switch net[3] { + if len(network) >= 4 { + switch network[3] { case '4': hints.Ai_family = syscall.AF_INET case '6': @@ -79,48 +85,56 @@ func cgoLookupPort(net, service string) (port int, err error, completed bool) { } s := syscall.StringBytePtr(service) + var res *syscall.Addrinfo syscall.Entersyscall() gerrno := libc_getaddrinfo(nil, s, &hints, &res) syscall.Exitsyscall() - if gerrno == 0 { - defer libc_freeaddrinfo(res) - for r := res; r != nil; r = r.Ai_next { - switch r.Ai_family { - default: - continue - case syscall.AF_INET: - sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.Ai_addr)) - p := (*[2]byte)(unsafe.Pointer(&sa.Port)) - return int(p[0])<<8 | int(p[1]), nil, true - case syscall.AF_INET6: - sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.Ai_addr)) - p := (*[2]byte)(unsafe.Pointer(&sa.Port)) - return int(p[0])<<8 | int(p[1]), nil, true + if gerrno != 0 { + switch gerrno { + case syscall.EAI_SYSTEM: + errno := syscall.GetErrno() + if errno == 0 { // see golang.org/issue/6232 + errno = syscall.EMFILE } + err = errno + default: + err = addrinfoErrno(gerrno) } + return 0, &DNSError{Err: err.Error(), Name: network + "/" + service}, true } - return 0, &AddrError{"unknown port", net + "/" + service}, true + defer libc_freeaddrinfo(res) + + for r := res; r != nil; r = r.Ai_next { + switch r.Ai_family { + case syscall.AF_INET: + sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.Ai_addr)) + p := (*[2]byte)(unsafe.Pointer(&sa.Port)) + return int(p[0])<<8 | int(p[1]), nil, true + case syscall.AF_INET6: + sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.Ai_addr)) + p := (*[2]byte)(unsafe.Pointer(&sa.Port)) + return int(p[0])<<8 | int(p[1]), nil, true + } + } + return 0, &DNSError{Err: "unknown port", Name: network + "/" + service}, true } -func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, completed bool) { +func cgoLookupIPCNAME(name string) (addrs []IPAddr, cname string, err error, completed bool) { acquireThread() defer releaseThread() - var res *syscall.Addrinfo var hints syscall.Addrinfo - - hints.Ai_flags = int32(cgoAddrInfoFlags()) + hints.Ai_flags = int32(cgoAddrInfoFlags) hints.Ai_socktype = syscall.SOCK_STREAM h := syscall.StringBytePtr(name) + var res *syscall.Addrinfo syscall.Entersyscall() gerrno := libc_getaddrinfo(h, nil, &hints, &res) syscall.Exitsyscall() if gerrno != 0 { - var str string - if gerrno == syscall.EAI_NONAME { - str = noSuchHost - } else if gerrno == syscall.EAI_SYSTEM { + switch gerrno { + case syscall.EAI_SYSTEM: errno := syscall.GetErrno() if errno == 0 { // err should not be nil, but sometimes getaddrinfo returns @@ -132,13 +146,16 @@ func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, complet // comes up again. golang.org/issue/6232. errno = syscall.EMFILE } - str = errno.Error() - } else { - str = bytePtrToString(libc_gai_strerror(gerrno)) + err = errno + case syscall.EAI_NONAME: + err = errNoSuchHost + default: + err = addrinfoErrno(gerrno) } - return nil, "", &DNSError{Err: str, Name: name}, true + return nil, "", &DNSError{Err: err.Error(), Name: name}, true } defer libc_freeaddrinfo(res) + if res != nil { cname = bytePtrToString((*byte)(unsafe.Pointer(res.Ai_canonname))) if cname == "" { @@ -154,20 +171,20 @@ func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, complet continue } switch r.Ai_family { - default: - continue case syscall.AF_INET: sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.Ai_addr)) - addrs = append(addrs, copyIP(sa.Addr[:])) + addr := IPAddr{IP: copyIP(sa.Addr[:])} + addrs = append(addrs, addr) case syscall.AF_INET6: sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.Ai_addr)) - addrs = append(addrs, copyIP(sa.Addr[:])) + addr := IPAddr{IP: copyIP(sa.Addr[:]), Zone: zoneToString(int(sa.Scope_id))} + addrs = append(addrs, addr) } } return addrs, cname, nil, true } -func cgoLookupIP(name string) (addrs []IP, err error, completed bool) { +func cgoLookupIP(name string) (addrs []IPAddr, err error, completed bool) { addrs, _, err, completed = cgoLookupIPCNAME(name) return } @@ -177,6 +194,77 @@ func cgoLookupCNAME(name string) (cname string, err error, completed bool) { return } +// These are roughly enough for the following: +// +// Source Encoding Maximum length of single name entry +// Unicast DNS ASCII or <=253 + a NUL terminator +// Unicode in RFC 5892 252 * total number of labels + delimiters + a NUL terminator +// Multicast DNS UTF-8 in RFC 5198 or <=253 + a NUL terminator +// the same as unicast DNS ASCII <=253 + a NUL terminator +// Local database various depends on implementation +const ( + nameinfoLen = 64 + maxNameinfoLen = 4096 +) + +func cgoLookupPTR(addr string) ([]string, error, bool) { + acquireThread() + defer releaseThread() + + ip := ParseIP(addr) + if ip == nil { + return nil, &DNSError{Err: "invalid address", Name: addr}, true + } + sa, salen := cgoSockaddr(ip) + if sa == nil { + return nil, &DNSError{Err: "invalid address " + ip.String(), Name: addr}, true + } + var err error + var b []byte + var gerrno int + for l := nameinfoLen; l <= maxNameinfoLen; l *= 2 { + b = make([]byte, l) + gerrno, err = cgoNameinfoPTR(b, sa, salen) + if gerrno == 0 || gerrno != syscall.EAI_OVERFLOW { + break + } + } + if gerrno != 0 { + switch gerrno { + case syscall.EAI_SYSTEM: + if err == nil { // see golang.org/issue/6232 + err = syscall.EMFILE + } + default: + err = addrinfoErrno(gerrno) + } + return nil, &DNSError{Err: err.Error(), Name: addr}, true + } + + for i := 0; i < len(b); i++ { + if b[i] == 0 { + b = b[:i] + break + } + } + // Add trailing dot to match pure Go reverse resolver + // and all other lookup routines. See golang.org/issue/12189. + if len(b) > 0 && b[len(b)-1] != '.' { + b = append(b, '.') + } + return []string{string(b)}, nil, true +} + +func cgoSockaddr(ip IP) (*syscall.RawSockaddr, syscall.Socklen_t) { + if ip4 := ip.To4(); ip4 != nil { + return cgoSockaddrInet4(ip4), syscall.Socklen_t(syscall.SizeofSockaddrInet4) + } + if ip6 := ip.To16(); ip6 != nil { + return cgoSockaddrInet6(ip6), syscall.Socklen_t(syscall.SizeofSockaddrInet6) + } + return nil, 0 +} + func copyIP(x IP) IP { if len(x) < 16 { return x.To16() diff --git a/libgo/go/net/cgo_unix_test.go b/libgo/go/net/cgo_unix_test.go index 33566ce9c2e..4d5ab23fd36 100644 --- a/libgo/go/net/cgo_unix_test.go +++ b/libgo/go/net/cgo_unix_test.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build cgo,!netgo -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net @@ -16,9 +16,9 @@ func TestCgoLookupIP(t *testing.T) { t.Errorf("cgoLookupIP must not be a placeholder") } if err != nil { - t.Errorf("cgoLookupIP failed: %v", err) + t.Error(err) } if _, err := goLookupIP(host); err != nil { - t.Errorf("goLookupIP failed: %v", err) + t.Error(err) } } diff --git a/libgo/go/net/cgo_windows.go b/libgo/go/net/cgo_windows.go new file mode 100644 index 00000000000..8968b757a90 --- /dev/null +++ b/libgo/go/net/cgo_windows.go @@ -0,0 +1,13 @@ +// Copyright 2015 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 cgo,!netgo + +package net + +type addrinfoErrno int + +func (eai addrinfoErrno) Error() string { return "<nil>" } +func (eai addrinfoErrno) Temporary() bool { return false } +func (eai addrinfoErrno) Timeout() bool { return false } diff --git a/libgo/go/net/conf.go b/libgo/go/net/conf.go new file mode 100644 index 00000000000..c92e579d7e6 --- /dev/null +++ b/libgo/go/net/conf.go @@ -0,0 +1,308 @@ +// Copyright 2015 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 dragonfly freebsd linux netbsd openbsd solaris + +package net + +import ( + "os" + "runtime" + "strconv" + "sync" + "syscall" +) + +// conf represents a system's network configuration. +type conf struct { + // forceCgoLookupHost forces CGO to always be used, if available. + forceCgoLookupHost bool + + netGo bool // go DNS resolution forced + netCgo bool // cgo DNS resolution forced + + // machine has an /etc/mdns.allow file + hasMDNSAllow bool + + goos string // the runtime.GOOS, to ease testing + dnsDebugLevel int + + nss *nssConf + resolv *dnsConfig +} + +var ( + confOnce sync.Once // guards init of confVal via initConfVal + confVal = &conf{goos: runtime.GOOS} +) + +// systemConf returns the machine's network configuration. +func systemConf() *conf { + confOnce.Do(initConfVal) + return confVal +} + +func initConfVal() { + dnsMode, debugLevel := goDebugNetDNS() + confVal.dnsDebugLevel = debugLevel + confVal.netGo = netGo || dnsMode == "go" + confVal.netCgo = netCgo || dnsMode == "cgo" + + if confVal.dnsDebugLevel > 0 { + defer func() { + switch { + case confVal.netGo: + if netGo { + println("go package net: built with netgo build tag; using Go's DNS resolver") + } else { + println("go package net: GODEBUG setting forcing use of Go's resolver") + } + case confVal.forceCgoLookupHost: + println("go package net: using cgo DNS resolver") + default: + println("go package net: dynamic selection of DNS resolver") + } + }() + } + + // Darwin pops up annoying dialog boxes if programs try to do + // their own DNS requests. So always use cgo instead, which + // avoids that. + if runtime.GOOS == "darwin" { + confVal.forceCgoLookupHost = true + return + } + + // If any environment-specified resolver options are specified, + // force cgo. Note that LOCALDOMAIN can change behavior merely + // by being specified with the empty string. + _, localDomainDefined := syscall.Getenv("LOCALDOMAIN") + if os.Getenv("RES_OPTIONS") != "" || + os.Getenv("HOSTALIASES") != "" || + confVal.netCgo || + localDomainDefined { + confVal.forceCgoLookupHost = true + return + } + + // OpenBSD apparently lets you override the location of resolv.conf + // with ASR_CONFIG. If we notice that, defer to libc. + if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" { + confVal.forceCgoLookupHost = true + return + } + + if runtime.GOOS != "openbsd" { + confVal.nss = parseNSSConfFile("/etc/nsswitch.conf") + } + + confVal.resolv = dnsReadConfig("/etc/resolv.conf") + if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) && + !os.IsPermission(confVal.resolv.err) { + // If we can't read the resolv.conf file, assume it + // had something important in it and defer to cgo. + // libc's resolver might then fail too, but at least + // it wasn't our fault. + confVal.forceCgoLookupHost = true + } + + if _, err := os.Stat("/etc/mdns.allow"); err == nil { + confVal.hasMDNSAllow = true + } +} + +// canUseCgo reports whether calling cgo functions is allowed +// for non-hostname lookups. +func (c *conf) canUseCgo() bool { + return c.hostLookupOrder("") == hostLookupCgo +} + +// hostLookupOrder determines which strategy to use to resolve hostname. +func (c *conf) hostLookupOrder(hostname string) (ret hostLookupOrder) { + if c.dnsDebugLevel > 1 { + defer func() { + print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") + }() + } + if c.netGo { + return hostLookupFilesDNS + } + if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" { + return hostLookupCgo + } + if byteIndex(hostname, '\\') != -1 || byteIndex(hostname, '%') != -1 { + // Don't deal with special form hostnames with backslashes + // or '%'. + return hostLookupCgo + } + + // OpenBSD is unique and doesn't use nsswitch.conf. + // It also doesn't support mDNS. + if c.goos == "openbsd" { + // OpenBSD's resolv.conf manpage says that a non-existent + // resolv.conf means "lookup" defaults to only "files", + // without DNS lookups. + if os.IsNotExist(c.resolv.err) { + return hostLookupFiles + } + lookup := c.resolv.lookup + if len(lookup) == 0 { + // http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5 + // "If the lookup keyword is not used in the + // system's resolv.conf file then the assumed + // order is 'bind file'" + return hostLookupDNSFiles + } + if len(lookup) < 1 || len(lookup) > 2 { + return hostLookupCgo + } + switch lookup[0] { + case "bind": + if len(lookup) == 2 { + if lookup[1] == "file" { + return hostLookupDNSFiles + } + return hostLookupCgo + } + return hostLookupDNS + case "file": + if len(lookup) == 2 { + if lookup[1] == "bind" { + return hostLookupFilesDNS + } + return hostLookupCgo + } + return hostLookupFiles + default: + return hostLookupCgo + } + } + + hasDot := byteIndex(hostname, '.') != -1 + + // Canonicalize the hostname by removing any trailing dot. + if stringsHasSuffix(hostname, ".") { + hostname = hostname[:len(hostname)-1] + } + if stringsHasSuffixFold(hostname, ".local") { + // Per RFC 6762, the ".local" TLD is special. And + // because Go's native resolver doesn't do mDNS or + // similar local resolution mechanisms, assume that + // libc might (via Avahi, etc) and use cgo. + return hostLookupCgo + } + + nss := c.nss + srcs := nss.sources["hosts"] + // If /etc/nsswitch.conf doesn't exist or doesn't specify any + // sources for "hosts", assume Go's DNS will work fine. + if os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) { + if c.goos == "solaris" { + // illumos defaults to "nis [NOTFOUND=return] files" + return hostLookupCgo + } + if c.goos == "linux" { + // glibc says the default is "dns [!UNAVAIL=return] files" + // http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html. + return hostLookupDNSFiles + } + return hostLookupFilesDNS + } + if nss.err != nil { + // We failed to parse or open nsswitch.conf, so + // conservatively assume we should use cgo if it's + // available. + return hostLookupCgo + } + + var mdnsSource, filesSource, dnsSource bool + var first string + for _, src := range srcs { + if src.source == "myhostname" { + if hasDot { + continue + } + return hostLookupCgo + } + if src.source == "files" || src.source == "dns" { + if !src.standardCriteria() { + return hostLookupCgo // non-standard; let libc deal with it. + } + if src.source == "files" { + filesSource = true + } else if src.source == "dns" { + dnsSource = true + } + if first == "" { + first = src.source + } + continue + } + if stringsHasPrefix(src.source, "mdns") { + // e.g. "mdns4", "mdns4_minimal" + // We already returned true before if it was *.local. + // libc wouldn't have found a hit on this anyway. + mdnsSource = true + continue + } + // Some source we don't know how to deal with. + return hostLookupCgo + } + + // We don't parse mdns.allow files. They're rare. If one + // exists, it might list other TLDs (besides .local) or even + // '*', so just let libc deal with it. + if mdnsSource && c.hasMDNSAllow { + return hostLookupCgo + } + + // Cases where Go can handle it without cgo and C thread + // overhead. + switch { + case filesSource && dnsSource: + if first == "files" { + return hostLookupFilesDNS + } else { + return hostLookupDNSFiles + } + case filesSource: + return hostLookupFiles + case dnsSource: + return hostLookupDNS + } + + // Something weird. Let libc deal with it. + return hostLookupCgo +} + +// goDebugNetDNS parses the value of the GODEBUG "netdns" value. +// The netdns value can be of the form: +// 1 // debug level 1 +// 2 // debug level 2 +// cgo // use cgo for DNS lookups +// go // use go for DNS lookups +// cgo+1 // use cgo for DNS lookups + debug level 1 +// 1+cgo // same +// cgo+2 // same, but debug level 2 +// etc. +func goDebugNetDNS() (dnsMode string, debugLevel int) { + goDebug := goDebugString("netdns") + parsePart := func(s string) { + if s == "" { + return + } + if '0' <= s[0] && s[0] <= '9' { + debugLevel, _ = strconv.Atoi(s) + } else { + dnsMode = s + } + } + if i := byteIndex(goDebug, '+'); i != -1 { + parsePart(goDebug[:i]) + parsePart(goDebug[i+1:]) + return + } + parsePart(goDebug) + return +} diff --git a/libgo/go/net/conf_netcgo.go b/libgo/go/net/conf_netcgo.go new file mode 100644 index 00000000000..b66bae37106 --- /dev/null +++ b/libgo/go/net/conf_netcgo.go @@ -0,0 +1,17 @@ +// Copyright 2015 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 netcgo + +package net + +/* + +// Fail if cgo isn't available. + +*/ + +// The build tag "netcgo" forces use of the cgo DNS resolver. +// It is the opposite of "netgo". +func init() { netCgo = true } diff --git a/libgo/go/net/conf_test.go b/libgo/go/net/conf_test.go new file mode 100644 index 00000000000..86904bffde7 --- /dev/null +++ b/libgo/go/net/conf_test.go @@ -0,0 +1,301 @@ +// Copyright 2015 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 dragonfly freebsd linux netbsd openbsd solaris + +package net + +import ( + "os" + "strings" + "testing" +) + +type nssHostTest struct { + host string + want hostLookupOrder +} + +func nssStr(s string) *nssConf { return parseNSSConf(strings.NewReader(s)) } + +// represents a dnsConfig returned by parsing a nonexistent resolv.conf +var defaultResolvConf = &dnsConfig{ + servers: defaultNS, + ndots: 1, + timeout: 5, + attempts: 2, + err: os.ErrNotExist, +} + +func TestConfHostLookupOrder(t *testing.T) { + tests := []struct { + name string + c *conf + goos string + hostTests []nssHostTest + }{ + { + name: "force", + c: &conf{ + forceCgoLookupHost: true, + nss: nssStr("foo: bar"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"foo.local", hostLookupCgo}, + {"google.com", hostLookupCgo}, + }, + }, + { + name: "ubuntu_trusty_avahi", + c: &conf{ + nss: nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"foo.local", hostLookupCgo}, + {"foo.local.", hostLookupCgo}, + {"foo.LOCAL", hostLookupCgo}, + {"foo.LOCAL.", hostLookupCgo}, + {"google.com", hostLookupFilesDNS}, + }, + }, + { + name: "freebsdlinux_no_resolv_conf", + c: &conf{ + goos: "freebsd", + nss: nssStr("foo: bar"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}}, + }, + // On OpenBSD, no resolv.conf means no DNS. + { + name: "openbsd_no_resolv_conf", + c: &conf{ + goos: "openbsd", + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{{"google.com", hostLookupFiles}}, + }, + { + name: "solaris_no_nsswitch", + c: &conf{ + goos: "solaris", + nss: &nssConf{err: os.ErrNotExist}, + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{{"google.com", hostLookupCgo}}, + }, + { + name: "openbsd_lookup_bind_file", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: []string{"bind", "file"}}, + }, + hostTests: []nssHostTest{ + {"google.com", hostLookupDNSFiles}, + {"foo.local", hostLookupDNSFiles}, + }, + }, + { + name: "openbsd_lookup_file_bind", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: []string{"file", "bind"}}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}}, + }, + { + name: "openbsd_lookup_bind", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: []string{"bind"}}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupDNS}}, + }, + { + name: "openbsd_lookup_file", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: []string{"file"}}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupFiles}}, + }, + { + name: "openbsd_lookup_yp", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: []string{"file", "bind", "yp"}}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupCgo}}, + }, + { + name: "openbsd_lookup_two", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: []string{"file", "foo"}}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupCgo}}, + }, + { + name: "openbsd_lookup_empty", + c: &conf{ + goos: "openbsd", + resolv: &dnsConfig{lookup: nil}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}}, + }, + // glibc lacking an nsswitch.conf, per + // http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html + { + name: "linux_no_nsswitch.conf", + c: &conf{ + goos: "linux", + nss: &nssConf{err: os.ErrNotExist}, + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}}, + }, + { + name: "files_mdns_dns", + c: &conf{ + nss: nssStr("hosts: files mdns dns"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupFilesDNS}, + {"x.local", hostLookupCgo}, + }, + }, + { + name: "dns_special_hostnames", + c: &conf{ + nss: nssStr("hosts: dns"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupDNS}, + {"x\\.com", hostLookupCgo}, // punt on weird glibc escape + {"foo.com%en0", hostLookupCgo}, // and IPv6 zones + }, + }, + { + name: "mdns_allow", + c: &conf{ + nss: nssStr("hosts: files mdns dns"), + resolv: defaultResolvConf, + hasMDNSAllow: true, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupCgo}, + {"x.local", hostLookupCgo}, + }, + }, + { + name: "files_dns", + c: &conf{ + nss: nssStr("hosts: files dns"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupFilesDNS}, + {"x", hostLookupFilesDNS}, + {"x.local", hostLookupCgo}, + }, + }, + { + name: "dns_files", + c: &conf{ + nss: nssStr("hosts: dns files"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupDNSFiles}, + {"x", hostLookupDNSFiles}, + {"x.local", hostLookupCgo}, + }, + }, + { + name: "something_custom", + c: &conf{ + nss: nssStr("hosts: dns files something_custom"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupCgo}, + }, + }, + { + name: "myhostname", + c: &conf{ + nss: nssStr("hosts: files dns myhostname"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupFilesDNS}, + {"somehostname", hostLookupCgo}, + }, + }, + { + name: "ubuntu14.04.02", + c: &conf{ + nss: nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupFilesDNS}, + {"somehostname", hostLookupCgo}, + }, + }, + // Debian Squeeze is just "dns,files", but lists all + // the default criteria for dns, but then has a + // non-standard but redundant notfound=return for the + // files. + { + name: "debian_squeeze", + c: &conf{ + nss: nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupDNSFiles}, + {"somehostname", hostLookupDNSFiles}, + }, + }, + { + name: "resolv.conf-unknown", + c: &conf{ + nss: nssStr("foo: bar"), + resolv: &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true}, + }, + hostTests: []nssHostTest{{"google.com", hostLookupCgo}}, + }, + // Android should always use cgo. + { + name: "android", + c: &conf{ + goos: "android", + nss: nssStr(""), + resolv: defaultResolvConf, + }, + hostTests: []nssHostTest{ + {"x.com", hostLookupCgo}, + }, + }, + } + for _, tt := range tests { + for _, ht := range tt.hostTests { + gotOrder := tt.c.hostLookupOrder(ht.host) + if gotOrder != ht.want { + t.Errorf("%s: hostLookupOrder(%q) = %v; want %v", tt.name, ht.host, gotOrder, ht.want) + } + } + } + +} + +func TestSystemConf(t *testing.T) { + systemConf() +} diff --git a/libgo/go/net/conn_test.go b/libgo/go/net/conn_test.go index 9c9d1a8057d..6995c110f20 100644 --- a/libgo/go/net/conn_test.go +++ b/libgo/go/net/conn_test.go @@ -8,117 +8,58 @@ package net import ( - "os" - "runtime" "testing" "time" ) -var connTests = []struct { - net string - addr string -}{ - {"tcp", "127.0.0.1:0"}, - {"unix", testUnixAddr()}, - {"unixpacket", testUnixAddr()}, -} - // someTimeout is used just to test that net.Conn implementations // don't explode when their SetFooDeadline methods are called. // It isn't actually used for testing timeouts. const someTimeout = 10 * time.Second func TestConnAndListener(t *testing.T) { - for _, tt := range connTests { - switch tt.net { - case "unix": - switch runtime.GOOS { - case "nacl", "plan9", "windows": - continue - } - case "unixpacket": - switch runtime.GOOS { - case "android", "darwin", "nacl", "openbsd", "plan9", "windows": - continue - case "freebsd": // FreeBSD 8 doesn't support unixpacket - continue - } + for i, network := range []string{"tcp", "unix", "unixpacket"} { + if !testableNetwork(network) { + t.Logf("skipping %s test", network) + continue } - ln, err := Listen(tt.net, tt.addr) + ls, err := newLocalServer(network) if err != nil { - t.Fatalf("Listen failed: %v", err) + t.Fatal(err) } - defer func(ln Listener, net, addr string) { - ln.Close() - switch net { - case "unix", "unixpacket": - os.Remove(addr) - } - }(ln, tt.net, tt.addr) - if ln.Addr().Network() != tt.net { - t.Fatalf("got %v; expected %v", ln.Addr().Network(), tt.net) + defer ls.teardown() + ch := make(chan error, 1) + handler := func(ls *localServer, ln Listener) { transponder(ln, ch) } + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } + if ls.Listener.Addr().Network() != network { + t.Fatalf("got %s; want %s", ls.Listener.Addr().Network(), network) } - done := make(chan int) - go transponder(t, ln, done) - - c, err := Dial(tt.net, ln.Addr().String()) + c, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c.Close() - if c.LocalAddr().Network() != tt.net || c.LocalAddr().Network() != tt.net { - t.Fatalf("got %v->%v; expected %v->%v", c.LocalAddr().Network(), c.RemoteAddr().Network(), tt.net, tt.net) + if c.LocalAddr().Network() != network || c.LocalAddr().Network() != network { + t.Fatalf("got %s->%s; want %s->%s", c.LocalAddr().Network(), c.RemoteAddr().Network(), network, network) } c.SetDeadline(time.Now().Add(someTimeout)) c.SetReadDeadline(time.Now().Add(someTimeout)) c.SetWriteDeadline(time.Now().Add(someTimeout)) - if _, err := c.Write([]byte("CONN TEST")); err != nil { - t.Fatalf("Conn.Write failed: %v", err) + if _, err := c.Write([]byte("CONN AND LISTENER TEST")); err != nil { + t.Fatal(err) } rb := make([]byte, 128) if _, err := c.Read(rb); err != nil { - t.Fatalf("Conn.Read failed: %v", err) + t.Fatal(err) } - <-done - } -} - -func transponder(t *testing.T, ln Listener, done chan<- int) { - defer func() { done <- 1 }() - - switch ln := ln.(type) { - case *TCPListener: - ln.SetDeadline(time.Now().Add(someTimeout)) - case *UnixListener: - ln.SetDeadline(time.Now().Add(someTimeout)) - } - c, err := ln.Accept() - if err != nil { - t.Errorf("Listener.Accept failed: %v", err) - return - } - defer c.Close() - network := ln.Addr().Network() - if c.LocalAddr().Network() != network || c.LocalAddr().Network() != network { - t.Errorf("got %v->%v; expected %v->%v", c.LocalAddr().Network(), c.RemoteAddr().Network(), network, network) - return - } - c.SetDeadline(time.Now().Add(someTimeout)) - c.SetReadDeadline(time.Now().Add(someTimeout)) - c.SetWriteDeadline(time.Now().Add(someTimeout)) - - b := make([]byte, 128) - n, err := c.Read(b) - if err != nil { - t.Errorf("Conn.Read failed: %v", err) - return - } - if _, err := c.Write(b[:n]); err != nil { - t.Errorf("Conn.Write failed: %v", err) - return + for err := range ch { + t.Errorf("#%d: %v", i, err) + } } } diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index e6f0436cdd3..cb4ec216d53 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -21,6 +21,9 @@ type Dialer struct { // // The default is no timeout. // + // When dialing a name with multiple IP addresses, the timeout + // may be divided between them. + // // With or without a timeout, the operating system may impose // its own earlier timeout. For instance, TCP timeouts are // often around 3 minutes. @@ -38,13 +41,17 @@ type Dialer struct { // If nil, a local address is automatically chosen. LocalAddr Addr - // DualStack allows a single dial to attempt to establish - // multiple IPv4 and IPv6 connections and to return the first - // established connection when the network is "tcp" and the - // destination is a host name that has multiple address family - // DNS records. + // DualStack enables RFC 6555-compliant "Happy Eyeballs" dialing + // when the network is "tcp" and the destination is a host name + // with both IPv4 and IPv6 addresses. This allows a client to + // tolerate networks where one address family is silently broken. DualStack bool + // FallbackDelay specifies the length of time to wait before + // spawning a fallback connection, when DualStack is enabled. + // If zero, a default delay of 300ms is used. + FallbackDelay time.Duration + // KeepAlive specifies the keep-alive period for an active // network connection. // If zero, keep-alives are not enabled. Network protocols @@ -54,11 +61,11 @@ type Dialer struct { // Return either now+Timeout or Deadline, whichever comes first. // Or zero, if neither is set. -func (d *Dialer) deadline() time.Time { +func (d *Dialer) deadline(now time.Time) time.Time { if d.Timeout == 0 { return d.Deadline } - timeoutDeadline := time.Now().Add(d.Timeout) + timeoutDeadline := now.Add(d.Timeout) if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) { return timeoutDeadline } else { @@ -66,6 +73,38 @@ func (d *Dialer) deadline() time.Time { } } +// partialDeadline returns the deadline to use for a single address, +// when multiple addresses are pending. +func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) { + if deadline.IsZero() { + return deadline, nil + } + timeRemaining := deadline.Sub(now) + if timeRemaining <= 0 { + return time.Time{}, errTimeout + } + // Tentatively allocate equal time to each remaining address. + timeout := timeRemaining / time.Duration(addrsRemaining) + // If the time per address is too short, steal from the end of the list. + const saneMinimum = 2 * time.Second + if timeout < saneMinimum { + if timeRemaining < saneMinimum { + timeout = timeRemaining + } else { + timeout = saneMinimum + } + } + return now.Add(timeout), nil +} + +func (d *Dialer) fallbackDelay() time.Duration { + if d.FallbackDelay > 0 { + return d.FallbackDelay + } else { + return 300 * time.Millisecond + } +} + func parseNetwork(net string) (afnet string, proto int, err error) { i := last(net, ':') if i < 0 { // no colon @@ -95,7 +134,7 @@ func parseNetwork(net string) (afnet string, proto int, err error) { return "", 0, UnknownNetworkError(net) } -func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) { +func resolveAddrList(op, net, addr string, deadline time.Time) (addrList, error) { afnet, _, err := parseNetwork(net) if err != nil { return nil, err @@ -105,9 +144,13 @@ func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) { } switch afnet { case "unix", "unixgram", "unixpacket": - return ResolveUnixAddr(afnet, addr) + addr, err := ResolveUnixAddr(afnet, addr) + if err != nil { + return nil, err + } + return addrList{addr}, nil } - return resolveInternetAddr(afnet, addr, deadline) + return internetAddrList(afnet, addr, deadline) } // Dial connects to the address on the named network. @@ -150,100 +193,186 @@ func DialTimeout(network, address string, timeout time.Duration) (Conn, error) { return d.Dial(network, address) } +// dialContext holds common state for all dial operations. +type dialContext struct { + Dialer + network, address string + finalDeadline time.Time +} + // Dial connects to the address on the named network. // // See func Dial for a description of the network and address // parameters. func (d *Dialer) Dial(network, address string) (Conn, error) { - ra, err := resolveAddr("dial", network, address, d.deadline()) + finalDeadline := d.deadline(time.Now()) + addrs, err := resolveAddrList("dial", network, address, finalDeadline) if err != nil { - return nil, &OpError{Op: "dial", Net: network, Addr: nil, Err: err} + return nil, &OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: err} } - dialer := func(deadline time.Time) (Conn, error) { - return dialSingle(network, address, d.LocalAddr, ra.toAddr(), deadline) + + ctx := &dialContext{ + Dialer: *d, + network: network, + address: address, + finalDeadline: finalDeadline, } - if ras, ok := ra.(addrList); ok && d.DualStack && network == "tcp" { - dialer = func(deadline time.Time) (Conn, error) { - return dialMulti(network, address, d.LocalAddr, ras, deadline) - } + + var primaries, fallbacks addrList + if d.DualStack && network == "tcp" { + primaries, fallbacks = addrs.partition(isIPv4) + } else { + primaries = addrs } - c, err := dial(network, ra.toAddr(), dialer, d.deadline()) + + var c Conn + if len(fallbacks) == 0 { + // dialParallel can accept an empty fallbacks list, + // but this shortcut avoids the goroutine/channel overhead. + c, err = dialSerial(ctx, primaries, nil) + } else { + c, err = dialParallel(ctx, primaries, fallbacks) + } + if d.KeepAlive > 0 && err == nil { if tc, ok := c.(*TCPConn); ok { - tc.SetKeepAlive(true) - tc.SetKeepAlivePeriod(d.KeepAlive) + setKeepAlive(tc.fd, true) + setKeepAlivePeriod(tc.fd, d.KeepAlive) testHookSetKeepAlive() } } return c, err } -var testHookSetKeepAlive = func() {} // changed by dial_test.go +// dialParallel races two copies of dialSerial, giving the first a +// head start. It returns the first established connection and +// closes the others. Otherwise it returns an error from the first +// primary address. +func dialParallel(ctx *dialContext, primaries, fallbacks addrList) (Conn, error) { + results := make(chan dialResult) // unbuffered, so dialSerialAsync can detect race loss & cleanup + cancel := make(chan struct{}) + defer close(cancel) + + // Spawn the primary racer. + go dialSerialAsync(ctx, primaries, nil, cancel, results) + + // Spawn the fallback racer. + fallbackTimer := time.NewTimer(ctx.fallbackDelay()) + go dialSerialAsync(ctx, fallbacks, fallbackTimer, cancel, results) -// dialMulti attempts to establish connections to each destination of -// the list of addresses. It will return the first established -// connection and close the other connections. Otherwise it returns -// error on the last attempt. -func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Conn, error) { - type racer struct { - Conn - error + var primaryErr error + for nracers := 2; nracers > 0; nracers-- { + res := <-results + // If we're still waiting for a connection, then hasten the delay. + // Otherwise, disable the Timer and let cancel take over. + if fallbackTimer.Stop() && res.error != nil { + fallbackTimer.Reset(0) + } + if res.error == nil { + return res.Conn, nil + } + if res.primary { + primaryErr = res.error + } } - // Sig controls the flow of dial results on lane. It passes a - // token to the next racer and also indicates the end of flow - // by using closed channel. - sig := make(chan bool, 1) - lane := make(chan racer, 1) - for _, ra := range ras { - go func(ra Addr) { - c, err := dialSingle(net, addr, la, ra, deadline) - if _, ok := <-sig; ok { - lane <- racer{c, err} - } else if err == nil { - // We have to return the resources - // that belong to the other - // connections here for avoiding - // unnecessary resource starvation. - c.Close() - } - }(ra.toAddr()) + return nil, primaryErr +} + +type dialResult struct { + Conn + error + primary bool +} + +// dialSerialAsync runs dialSerial after some delay, and returns the +// resulting connection through a channel. When racing two connections, +// the primary goroutine uses a nil timer to omit the delay. +func dialSerialAsync(ctx *dialContext, ras addrList, timer *time.Timer, cancel <-chan struct{}, results chan<- dialResult) { + if timer != nil { + // We're in the fallback goroutine; sleep before connecting. + select { + case <-timer.C: + case <-cancel: + return + } } - defer close(sig) - lastErr := errTimeout - nracers := len(ras) - for nracers > 0 { - sig <- true - racer := <-lane - if racer.error == nil { - return racer.Conn, nil + c, err := dialSerial(ctx, ras, cancel) + select { + case results <- dialResult{c, err, timer == nil}: + // We won the race. + case <-cancel: + // The other goroutine won the race. + if c != nil { + c.Close() + } + } +} + +// dialSerial connects to a list of addresses in sequence, returning +// either the first successful connection, or the first error. +func dialSerial(ctx *dialContext, ras addrList, cancel <-chan struct{}) (Conn, error) { + var firstErr error // The error from the first address is most relevant. + + for i, ra := range ras { + select { + case <-cancel: + return nil, &OpError{Op: "dial", Net: ctx.network, Source: ctx.LocalAddr, Addr: ra, Err: errCanceled} + default: + } + + partialDeadline, err := partialDeadline(time.Now(), ctx.finalDeadline, len(ras)-i) + if err != nil { + // Ran out of time. + if firstErr == nil { + firstErr = &OpError{Op: "dial", Net: ctx.network, Source: ctx.LocalAddr, Addr: ra, Err: err} + } + break } - lastErr = racer.error - nracers-- + + // dialTCP does not support cancelation (see golang.org/issue/11225), + // so if cancel fires, we'll continue trying to connect until the next + // timeout, or return a spurious connection for the caller to close. + dialer := func(d time.Time) (Conn, error) { + return dialSingle(ctx, ra, d) + } + c, err := dial(ctx.network, ra, dialer, partialDeadline) + if err == nil { + return c, nil + } + if firstErr == nil { + firstErr = err + } + } + + if firstErr == nil { + firstErr = &OpError{Op: "dial", Net: ctx.network, Source: nil, Addr: nil, Err: errMissingAddress} } - return nil, lastErr + return nil, firstErr } // dialSingle attempts to establish and returns a single connection to -// the destination address. -func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) { +// the destination address. This must be called through the OS-specific +// dial function, because some OSes don't implement the deadline feature. +func dialSingle(ctx *dialContext, ra Addr, deadline time.Time) (c Conn, err error) { + la := ctx.LocalAddr if la != nil && la.Network() != ra.Network() { - return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())} + return nil, &OpError{Op: "dial", Net: ctx.network, Source: la, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())} } switch ra := ra.(type) { case *TCPAddr: la, _ := la.(*TCPAddr) - c, err = dialTCP(net, la, ra, deadline) + c, err = testHookDialTCP(ctx.network, la, ra, deadline) case *UDPAddr: la, _ := la.(*UDPAddr) - c, err = dialUDP(net, la, ra, deadline) + c, err = dialUDP(ctx.network, la, ra, deadline) case *IPAddr: la, _ := la.(*IPAddr) - c, err = dialIP(net, la, ra, deadline) + c, err = dialIP(ctx.network, la, ra, deadline) case *UnixAddr: la, _ := la.(*UnixAddr) - c, err = dialUnix(net, la, ra, deadline) + c, err = dialUnix(ctx.network, la, ra, deadline) default: - return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}} + return nil, &OpError{Op: "dial", Net: ctx.network, Source: la, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: ctx.address}} } if err != nil { return nil, err // c is non-nil interface containing nil pointer @@ -256,18 +385,18 @@ func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err // "tcp6", "unix" or "unixpacket". // See Dial for the syntax of laddr. func Listen(net, laddr string) (Listener, error) { - la, err := resolveAddr("listen", net, laddr, noDeadline) + addrs, err := resolveAddrList("listen", net, laddr, noDeadline) if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: err} } var l Listener - switch la := la.toAddr().(type) { + switch la := addrs.first(isIPv4).(type) { case *TCPAddr: l, err = ListenTCP(net, la) case *UnixAddr: l, err = ListenUnix(net, la) default: - return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}} } if err != nil { return nil, err // l is non-nil interface containing nil pointer @@ -280,12 +409,12 @@ func Listen(net, laddr string) (Listener, error) { // "udp6", "ip", "ip4", "ip6" or "unixgram". // See Dial for the syntax of laddr. func ListenPacket(net, laddr string) (PacketConn, error) { - la, err := resolveAddr("listen", net, laddr, noDeadline) + addrs, err := resolveAddrList("listen", net, laddr, noDeadline) if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: err} } var l PacketConn - switch la := la.toAddr().(type) { + switch la := addrs.first(isIPv4).(type) { case *UDPAddr: l, err = ListenUDP(net, la) case *IPAddr: @@ -293,7 +422,7 @@ func ListenPacket(net, laddr string) (PacketConn, error) { case *UnixAddr: l, err = ListenUnixgram(net, la) default: - return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}} } if err != nil { return nil, err // l is non-nil interface containing nil pointer diff --git a/libgo/go/net/dial_gen.go b/libgo/go/net/dial_gen.go index ada6233003f..a628f714835 100644 --- a/libgo/go/net/dial_gen.go +++ b/libgo/go/net/dial_gen.go @@ -6,22 +6,18 @@ package net -import ( - "time" -) - -var testingIssue5349 bool // used during tests +import "time" // dialChannel is the simple pure-Go implementation of dial, still // used on operating systems where the deadline hasn't been pushed // down into the pollserver. (Plan 9 and some old versions of Windows) func dialChannel(net string, ra Addr, dialer func(time.Time) (Conn, error), deadline time.Time) (Conn, error) { - var timeout time.Duration - if !deadline.IsZero() { - timeout = deadline.Sub(time.Now()) + if deadline.IsZero() { + return dialer(noDeadline) } + timeout := deadline.Sub(time.Now()) if timeout <= 0 { - return dialer(noDeadline) + return nil, &OpError{Op: "dial", Net: net, Source: nil, Addr: ra, Err: errTimeout} } t := time.NewTimer(timeout) defer t.Stop() @@ -31,15 +27,13 @@ func dialChannel(net string, ra Addr, dialer func(time.Time) (Conn, error), dead } ch := make(chan racer, 1) go func() { - if testingIssue5349 { - time.Sleep(time.Millisecond) - } + testHookDialChannel() c, err := dialer(noDeadline) ch <- racer{c, err} }() select { case <-t.C: - return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errTimeout} + return nil, &OpError{Op: "dial", Net: net, Source: nil, Addr: ra, Err: errTimeout} case racer := <-ch: return racer.Conn, racer.error } diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index 42898d669f7..ed6d7cc42f1 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -5,111 +5,50 @@ package net import ( - "bytes" - "flag" - "fmt" "io" - "os" - "os/exec" - "reflect" - "regexp" + "net/internal/socktest" "runtime" - "strconv" "sync" "testing" "time" ) -func newLocalListener(t *testing.T) Listener { - ln, err := Listen("tcp", "127.0.0.1:0") - if err != nil { - ln, err = Listen("tcp6", "[::1]:0") +var prohibitionaryDialArgTests = []struct { + network string + address string +}{ + {"tcp6", "127.0.0.1"}, + {"tcp6", "::ffff:127.0.0.1"}, +} + +func TestProhibitionaryDialArg(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + if testing.Short() || !*testExternal { + t.Skip("avoid external network") } + if !supportsIPv4map { + t.Skip("mapping ipv4 address inside ipv6 address not supported") + } + + ln, err := Listen("tcp", "[::]:0") if err != nil { t.Fatal(err) } - return ln -} - -func TestDialTimeout(t *testing.T) { - origBacklog := listenerBacklog - defer func() { - listenerBacklog = origBacklog - }() - listenerBacklog = 1 - - ln := newLocalListener(t) defer ln.Close() - errc := make(chan error) - - numConns := listenerBacklog + 100 + _, port, err := SplitHostPort(ln.Addr().String()) + if err != nil { + t.Fatal(err) + } - // TODO(bradfitz): It's hard to test this in a portable - // way. This is unfortunate, but works for now. - switch runtime.GOOS { - case "linux": - // The kernel will start accepting TCP connections before userspace - // gets a chance to not accept them, so fire off a bunch to fill up - // the kernel's backlog. Then we test we get a failure after that. - for i := 0; i < numConns; i++ { - go func() { - _, err := DialTimeout("tcp", ln.Addr().String(), 200*time.Millisecond) - errc <- err - }() - } - case "darwin", "plan9", "windows": - // At least OS X 10.7 seems to accept any number of - // connections, ignoring listen's backlog, so resort - // to connecting to a hopefully-dead 127/8 address. - // Same for windows. - // - // Use an IANA reserved port (49151) instead of 80, because - // 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 - // <http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml> - go func() { - c, err := DialTimeout("tcp", "127.0.71.111:49151", 200*time.Millisecond) - if err == nil { - err = fmt.Errorf("unexpected: connected to %s!", c.RemoteAddr()) - c.Close() - } - errc <- err - }() - default: - // TODO(bradfitz): - // OpenBSD may have a reject route to 127/8 except 127.0.0.1/32 - // by default. FreeBSD likely works, but is untested. - // TODO(rsc): - // The timeout never happens on Windows. Why? Issue 3016. - t.Skipf("skipping test on %q; untested.", runtime.GOOS) - } - - connected := 0 - for { - select { - case <-time.After(15 * time.Second): - t.Fatal("too slow") - case err := <-errc: - if err == nil { - connected++ - if connected == numConns { - t.Fatal("all connections connected; expected some to time out") - } - } else { - terr, ok := err.(timeout) - if !ok { - t.Fatalf("got error %q; want error with timeout interface", err) - } - if !terr.Timeout() { - t.Fatalf("got error %q; not a timeout", err) - } - // Pass. We saw a timeout error. - return - } + for i, tt := range prohibitionaryDialArgTests { + c, err := Dial(tt.network, JoinHostPort(tt.address, port)) + if err == nil { + c.Close() + t.Errorf("#%d: %v", i, err) } } } @@ -117,7 +56,7 @@ func TestDialTimeout(t *testing.T) { func TestSelfConnect(t *testing.T) { if runtime.GOOS == "windows" { // TODO(brainman): do not know why it hangs. - t.Skip("skipping known-broken test on windows") + t.Skip("known-broken test on windows") } // Test that Dial does not honor self-connects. @@ -160,303 +99,518 @@ func TestSelfConnect(t *testing.T) { } } -var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors") +func TestDialTimeoutFDLeak(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("%s does not have full support of socktest", runtime.GOOS) + } -type DialErrorTest struct { - Net string - Raddr string - Pattern string -} + const T = 100 * time.Millisecond -var dialErrorTests = []DialErrorTest{ - { - "datakit", "mh/astro/r70", - "dial datakit mh/astro/r70: unknown network datakit", - }, - { - "tcp", "127.0.0.1:☺", - "dial tcp 127.0.0.1:☺: unknown port tcp/☺", - }, - { - "tcp", "no-such-name.google.com.:80", - "dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)", - }, - { - "tcp", "no-such-name.no-such-top-level-domain.:80", - "dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)", - }, - { - "tcp", "no-such-name:80", - `dial tcp no-such-name:80: lookup no-such-name\.(.*\.)?( on .*)?: no (.*)`, - }, - { - "tcp", "mh/astro/r70:http", - "dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name", - }, - { - "unix", "/etc/file-not-found", - "dial unix /etc/file-not-found: no such file or directory", - }, - { - "unix", "/etc/", - "dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)", - }, - { - "unixpacket", "/etc/file-not-found", - "dial unixpacket /etc/file-not-found: no such file or directory", - }, - { - "unixpacket", "/etc/", - "dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)", - }, -} + switch runtime.GOOS { + case "plan9", "windows": + origTestHookDialChannel := testHookDialChannel + testHookDialChannel = func() { time.Sleep(2 * T) } + defer func() { testHookDialChannel = origTestHookDialChannel }() + if runtime.GOOS == "plan9" { + break + } + fallthrough + default: + sw.Set(socktest.FilterConnect, func(so *socktest.Status) (socktest.AfterFilter, error) { + time.Sleep(2 * T) + return nil, errTimeout + }) + defer sw.Set(socktest.FilterConnect, nil) + } + + // Avoid tracking open-close jitterbugs between netFD and + // socket that leads to confusion of information inside + // socktest.Switch. + // It may happen when the Dial call bumps against TCP + // simultaneous open. See selfConnect in tcpsock_posix.go. + defer func() { + sw.Set(socktest.FilterClose, nil) + forceCloseSockets() + }() + var mu sync.Mutex + var attempts int + sw.Set(socktest.FilterClose, func(so *socktest.Status) (socktest.AfterFilter, error) { + mu.Lock() + attempts++ + mu.Unlock() + return nil, errTimedout + }) -var duplicateErrorPattern = `dial (.*) dial (.*)` + const N = 100 + var wg sync.WaitGroup + wg.Add(N) + for i := 0; i < N; i++ { + go func() { + defer wg.Done() + // This dial never starts to send any SYN + // segment because of above socket filter and + // test hook. + c, err := DialTimeout("tcp", "127.0.0.1:0", T) + if err == nil { + t.Errorf("unexpectedly established: tcp:%s->%s", c.LocalAddr(), c.RemoteAddr()) + c.Close() + } + }() + } + wg.Wait() + if attempts < N { + t.Errorf("got %d; want >= %d", attempts, N) + } +} -func TestDialError(t *testing.T) { - if !*runErrorTest { - t.Logf("test disabled; use -run_error_test to enable") - return +func TestDialerDualStackFDLeak(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("%s does not have full support of socktest", runtime.GOOS) + case "windows": + t.Skipf("not implemented a way to cancel dial racers in TCP SYN-SENT state on %s", runtime.GOOS) } - for i, tt := range dialErrorTests { - c, err := Dial(tt.Net, tt.Raddr) - if c != nil { + if !supportsIPv4 || !supportsIPv6 { + t.Skip("both IPv4 and IPv6 are required") + } + + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = lookupLocalhost + handler := func(dss *dualStackServer, ln Listener) { + for { + c, err := ln.Accept() + if err != nil { + return + } c.Close() } - if err == nil { - t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern) - continue - } - s := err.Error() - match, _ := regexp.MatchString(tt.Pattern, s) - if !match { - t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern) - } - match, _ = regexp.MatchString(duplicateErrorPattern, s) - if match { - t.Errorf("#%d: %q, duplicate error return from Dial", i, s) - } + } + dss, err := newDualStackServer([]streamListener{ + {network: "tcp4", address: "127.0.0.1"}, + {network: "tcp6", address: "::1"}, + }) + if err != nil { + t.Fatal(err) + } + defer dss.teardown() + if err := dss.buildup(handler); err != nil { + t.Fatal(err) + } + + before := sw.Sockets() + const T = 100 * time.Millisecond + const N = 10 + var wg sync.WaitGroup + wg.Add(N) + d := &Dialer{DualStack: true, Timeout: T} + for i := 0; i < N; i++ { + go func() { + defer wg.Done() + c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port)) + if err != nil { + t.Error(err) + return + } + c.Close() + }() + } + wg.Wait() + time.Sleep(2 * T) // wait for the dial racers to stop + after := sw.Sockets() + if len(after) != len(before) { + t.Errorf("got %d; want %d", len(after), len(before)) } } -var invalidDialAndListenArgTests = []struct { - net string - addr string - err error -}{ - {"foo", "bar", &OpError{Op: "dial", Net: "foo", Addr: nil, Err: UnknownNetworkError("foo")}}, - {"baz", "", &OpError{Op: "listen", Net: "baz", Addr: nil, Err: UnknownNetworkError("baz")}}, - {"tcp", "", &OpError{Op: "dial", Net: "tcp", Addr: nil, Err: errMissingAddress}}, +// Define a pair of blackholed (IPv4, IPv6) addresses, for which dialTCP is +// expected to hang until the timeout elapses. These addresses are reserved +// for benchmarking by RFC 6890. +const ( + slowDst4 = "192.18.0.254" + slowDst6 = "2001:2::254" + slowTimeout = 1 * time.Second +) + +// In some environments, the slow IPs may be explicitly unreachable, and fail +// more quickly than expected. This test hook prevents dialTCP from returning +// before the deadline. +func slowDialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { + c, err := dialTCP(net, laddr, raddr, deadline) + if ParseIP(slowDst4).Equal(raddr.IP) || ParseIP(slowDst6).Equal(raddr.IP) { + time.Sleep(deadline.Sub(time.Now())) + } + return c, err } -func TestInvalidDialAndListenArgs(t *testing.T) { - for _, tt := range invalidDialAndListenArgTests { - var err error - switch tt.err.(*OpError).Op { - case "dial": - _, err = Dial(tt.net, tt.addr) - case "listen": - _, err = Listen(tt.net, tt.addr) +func dialClosedPort() (actual, expected time.Duration) { + // Estimate the expected time for this platform. + // On Windows, dialing a closed port takes roughly 1 second, + // but other platforms should be instantaneous. + if runtime.GOOS == "windows" { + expected = 1500 * time.Millisecond + } else { + expected = 95 * time.Millisecond + } + + l, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + return 999 * time.Hour, expected + } + addr := l.Addr().String() + l.Close() + // On OpenBSD, interference from TestSelfConnect is mysteriously + // causing the first attempt to hang for a few seconds, so we throw + // away the first result and keep the second. + for i := 1; ; i++ { + startTime := time.Now() + c, err := Dial("tcp", addr) + if err == nil { + c.Close() } - if !reflect.DeepEqual(tt.err, err) { - t.Fatalf("got %#v; expected %#v", err, tt.err) + elapsed := time.Now().Sub(startTime) + if i == 2 { + return elapsed, expected } } } -func TestDialTimeoutFDLeak(t *testing.T) { - if runtime.GOOS != "linux" { - // TODO(bradfitz): test on other platforms - t.Skipf("skipping test on %q", runtime.GOOS) +func TestDialParallel(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("avoid external network") + } + if !supportsIPv4 || !supportsIPv6 { + t.Skip("both IPv4 and IPv6 are required") } - ln := newLocalListener(t) - defer ln.Close() - - type connErr struct { - conn Conn - err error + closedPortDelay, expectClosedPortDelay := dialClosedPort() + if closedPortDelay > expectClosedPortDelay { + t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay) } - dials := listenerBacklog + 100 - // used to be listenerBacklog + 5, but was found to be unreliable, issue 4384. - maxGoodConnect := listenerBacklog + runtime.NumCPU()*10 - 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} - }() + + const instant time.Duration = 0 + const fallbackDelay = 200 * time.Millisecond + + // Some cases will run quickly when "connection refused" is fast, + // or trigger the fallbackDelay on Windows. This value holds the + // lesser of the two delays. + var closedPortOrFallbackDelay time.Duration + if closedPortDelay < fallbackDelay { + closedPortOrFallbackDelay = closedPortDelay + } else { + closedPortOrFallbackDelay = fallbackDelay } - 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 + origTestHookDialTCP := testHookDialTCP + defer func() { testHookDialTCP = origTestHookDialTCP }() + testHookDialTCP = slowDialTCP + + nCopies := func(s string, n int) []string { + out := make([]string, n) + for i := 0; i < n; i++ { + out[i] = s } - 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) + return out + } + + var testCases = []struct { + primaries []string + fallbacks []string + teardownNetwork string + expectOk bool + expectElapsed time.Duration + }{ + // These should just work on the first try. + {[]string{"127.0.0.1"}, []string{}, "", true, instant}, + {[]string{"::1"}, []string{}, "", true, instant}, + {[]string{"127.0.0.1", "::1"}, []string{slowDst6}, "tcp6", true, instant}, + {[]string{"::1", "127.0.0.1"}, []string{slowDst4}, "tcp4", true, instant}, + // Primary is slow; fallback should kick in. + {[]string{slowDst4}, []string{"::1"}, "", true, fallbackDelay}, + // Skip a "connection refused" in the primary thread. + {[]string{"127.0.0.1", "::1"}, []string{}, "tcp4", true, closedPortDelay}, + {[]string{"::1", "127.0.0.1"}, []string{}, "tcp6", true, closedPortDelay}, + // Skip a "connection refused" in the fallback thread. + {[]string{slowDst4, slowDst6}, []string{"::1", "127.0.0.1"}, "tcp6", true, fallbackDelay + closedPortDelay}, + // Primary refused, fallback without delay. + {[]string{"127.0.0.1"}, []string{"::1"}, "tcp4", true, closedPortOrFallbackDelay}, + {[]string{"::1"}, []string{"127.0.0.1"}, "tcp6", true, closedPortOrFallbackDelay}, + // Everything is refused. + {[]string{"127.0.0.1"}, []string{}, "tcp4", false, closedPortDelay}, + // Nothing to do; fail instantly. + {[]string{}, []string{}, "", false, instant}, + // Connecting to tons of addresses should not trip the deadline. + {nCopies("::1", 1000), []string{}, "", true, instant}, + } + + handler := func(dss *dualStackServer, ln Listener) { + for { + c, err := ln.Accept() + if err != nil { + return + } + c.Close() } } - for _, c := range toClose { - c.Close() - } - for i := 0; i < 100; i++ { - if got := numFD(); got < dials { - // Test passes. - return + + // Convert a list of IP strings into TCPAddrs. + makeAddrs := func(ips []string, port string) addrList { + var out addrList + for _, ip := range ips { + addr, err := ResolveTCPAddr("tcp", JoinHostPort(ip, port)) + if err != nil { + t.Fatal(err) + } + out = append(out, addr) } - time.Sleep(10 * time.Millisecond) + return out } - if got := numFD(); got >= dials { - t.Errorf("num fds after %d timeouts = %d; want <%d", dials, got, dials) + + for i, tt := range testCases { + dss, err := newDualStackServer([]streamListener{ + {network: "tcp4", address: "127.0.0.1"}, + {network: "tcp6", address: "::1"}, + }) + if err != nil { + t.Fatal(err) + } + defer dss.teardown() + if err := dss.buildup(handler); err != nil { + t.Fatal(err) + } + if tt.teardownNetwork != "" { + // Destroy one of the listening sockets, creating an unreachable port. + dss.teardownNetwork(tt.teardownNetwork) + } + + primaries := makeAddrs(tt.primaries, dss.port) + fallbacks := makeAddrs(tt.fallbacks, dss.port) + d := Dialer{ + FallbackDelay: fallbackDelay, + Timeout: slowTimeout, + } + ctx := &dialContext{ + Dialer: d, + network: "tcp", + address: "?", + finalDeadline: d.deadline(time.Now()), + } + startTime := time.Now() + c, err := dialParallel(ctx, primaries, fallbacks) + elapsed := time.Now().Sub(startTime) + + if c != nil { + c.Close() + } + + if tt.expectOk && err != nil { + t.Errorf("#%d: got %v; want nil", i, err) + } else if !tt.expectOk && err == nil { + t.Errorf("#%d: got nil; want non-nil", i) + } + + expectElapsedMin := tt.expectElapsed - 95*time.Millisecond + expectElapsedMax := tt.expectElapsed + 95*time.Millisecond + if !(elapsed >= expectElapsedMin) { + t.Errorf("#%d: got %v; want >= %v", i, elapsed, expectElapsedMin) + } else if !(elapsed <= expectElapsedMax) { + t.Errorf("#%d: got %v; want <= %v", i, elapsed, expectElapsedMax) + } } + // Wait for any slowDst4/slowDst6 connections to timeout. + time.Sleep(slowTimeout * 3 / 2) } -func numTCP() (ntcp, nopen, nclose int, err error) { - lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() - if err != nil { - return 0, 0, 0, err +func lookupSlowFast(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { + switch host { + case "slow6loopback4": + // Returns a slow IPv6 address, and a local IPv4 address. + return []IPAddr{ + {IP: ParseIP(slowDst6)}, + {IP: ParseIP("127.0.0.1")}, + }, nil + default: + return fn(host) } - ntcp += bytes.Count(lsof, []byte("TCP")) - for _, state := range []string{"LISTEN", "SYN_SENT", "SYN_RECEIVED", "ESTABLISHED"} { - nopen += bytes.Count(lsof, []byte(state)) +} + +func TestDialerFallbackDelay(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("avoid external network") } - for _, state := range []string{"CLOSED", "CLOSE_WAIT", "LAST_ACK", "FIN_WAIT_1", "FIN_WAIT_2", "CLOSING", "TIME_WAIT"} { - nclose += bytes.Count(lsof, []byte(state)) + if !supportsIPv4 || !supportsIPv6 { + t.Skip("both IPv4 and IPv6 are required") } - return ntcp, nopen, nclose, nil -} -func TestDialMultiFDLeak(t *testing.T) { - t.Skip("flaky test - golang.org/issue/8764") + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = lookupSlowFast - if !supportsIPv4 || !supportsIPv6 { - t.Skip("neither ipv4 nor ipv6 is supported") + origTestHookDialTCP := testHookDialTCP + defer func() { testHookDialTCP = origTestHookDialTCP }() + testHookDialTCP = slowDialTCP + + var testCases = []struct { + dualstack bool + delay time.Duration + expectElapsed time.Duration + }{ + // Use a very brief delay, which should fallback immediately. + {true, 1 * time.Nanosecond, 0}, + // Use a 200ms explicit timeout. + {true, 200 * time.Millisecond, 200 * time.Millisecond}, + // The default is 300ms. + {true, 0, 300 * time.Millisecond}, + // This case is last, in order to wait for hanging slowDst6 connections. + {false, 0, slowTimeout}, } - halfDeadServer := func(dss *dualStackServer, ln Listener) { + handler := func(dss *dualStackServer, ln Listener) { for { - if c, err := ln.Accept(); err != nil { + c, err := ln.Accept() + if err != nil { return - } else { - // It just keeps established - // connections like a half-dead server - // does. - dss.putConn(c) } + c.Close() } } dss, err := newDualStackServer([]streamListener{ - {net: "tcp4", addr: "127.0.0.1"}, - {net: "tcp6", addr: "[::1]"}, + {network: "tcp", address: "127.0.0.1"}, }) if err != nil { - t.Fatalf("newDualStackServer failed: %v", err) + t.Fatal(err) } defer dss.teardown() - if err := dss.buildup(halfDeadServer); err != nil { - t.Fatalf("dualStackServer.buildup failed: %v", err) + if err := dss.buildup(handler); err != nil { + t.Fatal(err) } - _, before, _, err := numTCP() + for i, tt := range testCases { + d := &Dialer{DualStack: tt.dualstack, FallbackDelay: tt.delay, Timeout: slowTimeout} + + startTime := time.Now() + c, err := d.Dial("tcp", JoinHostPort("slow6loopback4", dss.port)) + elapsed := time.Now().Sub(startTime) + if err == nil { + c.Close() + } else if tt.dualstack { + t.Error(err) + } + expectMin := tt.expectElapsed - 1*time.Millisecond + expectMax := tt.expectElapsed + 95*time.Millisecond + if !(elapsed >= expectMin) { + t.Errorf("#%d: got %v; want >= %v", i, elapsed, expectMin) + } + if !(elapsed <= expectMax) { + t.Errorf("#%d: got %v; want <= %v", i, elapsed, expectMax) + } + } +} + +func TestDialSerialAsyncSpuriousConnection(t *testing.T) { + ln, err := newLocalListener("tcp") if err != nil { - t.Skipf("skipping test; error finding or running lsof: %v", err) + t.Fatal(err) } + defer ln.Close() - var wg sync.WaitGroup - portnum, _, _ := dtoi(dss.port, 0) - ras := addrList{ - // Losers that will fail to connect, see RFC 6890. - &TCPAddr{IP: IPv4(198, 18, 0, 254), Port: portnum}, - &TCPAddr{IP: ParseIP("2001:2::254"), Port: portnum}, - - // Winner candidates of this race. - &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum}, - &TCPAddr{IP: IPv6loopback, Port: portnum}, - - // Losers that will have established connections. - &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum}, - &TCPAddr{IP: IPv6loopback, Port: portnum}, - } - const T1 = 10 * time.Millisecond - const T2 = 2 * T1 - const N = 10 - for i := 0; i < N; i++ { - wg.Add(1) - go func() { - defer wg.Done() - if c, err := dialMulti("tcp", "fast failover test", nil, ras, time.Now().Add(T1)); err == nil { - c.Close() - } - }() + d := Dialer{} + ctx := &dialContext{ + Dialer: d, + network: "tcp", + address: "?", + finalDeadline: d.deadline(time.Now()), } - wg.Wait() - time.Sleep(T2) - ntcp, after, nclose, err := numTCP() + results := make(chan dialResult) + cancel := make(chan struct{}) + + // Spawn a connection in the background. + go dialSerialAsync(ctx, addrList{ln.Addr()}, nil, cancel, results) + + // Receive it at the server. + c, err := ln.Accept() if err != nil { - t.Skipf("skipping test; error finding or running lsof: %v", err) + t.Fatal(err) } - t.Logf("tcp sessions: %v, open sessions: %v, closing sessions: %v", ntcp, after, nclose) + defer c.Close() - if after != before { - t.Fatalf("got %v open sessions; expected %v", after, before) + // Tell dialSerialAsync that someone else won the race. + close(cancel) + + // The connection should close itself, without sending data. + c.SetReadDeadline(time.Now().Add(1 * time.Second)) + var b [1]byte + if _, err := c.Read(b[:]); err != io.EOF { + t.Errorf("got %v; want %v", err, io.EOF) } } -func numFD() int { - if runtime.GOOS == "linux" { - f, err := os.Open("/proc/self/fd") - if err != nil { - panic(err) +func TestDialerPartialDeadline(t *testing.T) { + now := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + var testCases = []struct { + now time.Time + deadline time.Time + addrs int + expectDeadline time.Time + expectErr error + }{ + // Regular division. + {now, now.Add(12 * time.Second), 1, now.Add(12 * time.Second), nil}, + {now, now.Add(12 * time.Second), 2, now.Add(6 * time.Second), nil}, + {now, now.Add(12 * time.Second), 3, now.Add(4 * time.Second), nil}, + // Bump against the 2-second sane minimum. + {now, now.Add(12 * time.Second), 999, now.Add(2 * time.Second), nil}, + // Total available is now below the sane minimum. + {now, now.Add(1900 * time.Millisecond), 999, now.Add(1900 * time.Millisecond), nil}, + // Null deadline. + {now, noDeadline, 1, noDeadline, nil}, + // Step the clock forward and cross the deadline. + {now.Add(-1 * time.Millisecond), now, 1, now, nil}, + {now.Add(0 * time.Millisecond), now, 1, noDeadline, errTimeout}, + {now.Add(1 * time.Millisecond), now, 1, noDeadline, errTimeout}, + } + for i, tt := range testCases { + deadline, err := partialDeadline(tt.now, tt.deadline, tt.addrs) + if err != tt.expectErr { + t.Errorf("#%d: got %v; want %v", i, err, tt.expectErr) } - defer f.Close() - names, err := f.Readdirnames(0) - if err != nil { - panic(err) + if deadline != tt.expectDeadline { + t.Errorf("#%d: got %v; want %v", i, deadline, tt.expectDeadline) } - return len(names) } - // All tests using this should be skipped anyway, but: - panic("numFDs not implemented on " + runtime.GOOS) } -func TestDialer(t *testing.T) { - ln, err := Listen("tcp4", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen failed: %v", err) - } - defer ln.Close() +func TestDialerLocalAddr(t *testing.T) { ch := make(chan error, 1) - go func() { + handler := func(ls *localServer, ln Listener) { c, err := ln.Accept() if err != nil { - ch <- fmt.Errorf("Accept failed: %v", err) + ch <- err return } defer c.Close() ch <- nil - }() + } + ls, err := newLocalServer("tcp") + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } - laddr, err := ResolveTCPAddr("tcp4", "127.0.0.1:0") + laddr, err := ResolveTCPAddr(ls.Listener.Addr().Network(), ls.Listener.Addr().String()) if err != nil { - t.Fatalf("ResolveTCPAddr failed: %v", err) + t.Fatal(err) } + laddr.Port = 0 d := &Dialer{LocalAddr: laddr} - c, err := d.Dial("tcp4", ln.Addr().String()) + c, err := d.Dial(ls.Listener.Addr().Network(), ls.Addr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c.Close() c.Read(make([]byte, 1)) @@ -466,61 +620,64 @@ func TestDialer(t *testing.T) { } } -func TestDialDualStackLocalhost(t *testing.T) { - switch runtime.GOOS { - case "nacl": - t.Skipf("skipping test on %q", runtime.GOOS) +func TestDialerDualStack(t *testing.T) { + if !supportsIPv4 || !supportsIPv6 { + t.Skip("both IPv4 and IPv6 are required") } - if ips, err := LookupIP("localhost"); err != nil { - t.Fatalf("LookupIP failed: %v", err) - } else if len(ips) < 2 || !supportsIPv4 || !supportsIPv6 { - t.Skip("localhost doesn't have a pair of different address family IP addresses") + closedPortDelay, expectClosedPortDelay := dialClosedPort() + if closedPortDelay > expectClosedPortDelay { + t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay) } - touchAndByeServer := func(dss *dualStackServer, ln Listener) { + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = lookupLocalhost + handler := func(dss *dualStackServer, ln Listener) { for { - if c, err := ln.Accept(); err != nil { + c, err := ln.Accept() + if err != nil { return - } else { - c.Close() } + c.Close() } } - dss, err := newDualStackServer([]streamListener{ - {net: "tcp4", addr: "127.0.0.1"}, - {net: "tcp6", addr: "[::1]"}, - }) - if err != nil { - t.Fatalf("newDualStackServer failed: %v", err) - } - defer dss.teardown() - if err := dss.buildup(touchAndByeServer); err != nil { - t.Fatalf("dualStackServer.buildup failed: %v", err) - } - d := &Dialer{DualStack: true} - for range dss.lns { - if c, err := d.Dial("tcp", "localhost:"+dss.port); err != nil { - t.Errorf("Dial failed: %v", err) - } else { - if addr := c.LocalAddr().(*TCPAddr); addr.IP.To4() != nil { + var timeout = 100*time.Millisecond + closedPortDelay + for _, dualstack := range []bool{false, true} { + dss, err := newDualStackServer([]streamListener{ + {network: "tcp4", address: "127.0.0.1"}, + {network: "tcp6", address: "::1"}, + }) + if err != nil { + t.Fatal(err) + } + defer dss.teardown() + if err := dss.buildup(handler); err != nil { + t.Fatal(err) + } + + d := &Dialer{DualStack: dualstack, Timeout: timeout} + for range dss.lns { + c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port)) + if err != nil { + t.Error(err) + continue + } + switch addr := c.LocalAddr().(*TCPAddr); { + case addr.IP.To4() != nil: dss.teardownNetwork("tcp4") - } else if addr.IP.To16() != nil && addr.IP.To4() == nil { + case addr.IP.To16() != nil && addr.IP.To4() == nil: dss.teardownNetwork("tcp6") } c.Close() } } + time.Sleep(timeout * 3 / 2) // wait for the dial racers to stop } func TestDialerKeepAlive(t *testing.T) { - ln := newLocalListener(t) - defer ln.Close() - defer func() { - testHookSetKeepAlive = func() {} - }() - go func() { + handler := func(ls *localServer, ln Listener) { for { c, err := ln.Accept() if err != nil { @@ -528,7 +685,17 @@ func TestDialerKeepAlive(t *testing.T) { } c.Close() } - }() + } + ls, err := newLocalServer("tcp") + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } + defer func() { testHookSetKeepAlive = func() {} }() + for _, keepAlive := range []bool{false, true} { got := false testHookSetKeepAlive = func() { got = true } @@ -536,7 +703,7 @@ func TestDialerKeepAlive(t *testing.T) { if keepAlive { d.KeepAlive = 30 * time.Second } - c, err := d.Dial("tcp", ln.Addr().String()) + c, err := d.Dial("tcp", ls.Listener.Addr().String()) if err != nil { t.Fatal(err) } diff --git a/libgo/go/net/dialgoogle_test.go b/libgo/go/net/dialgoogle_test.go deleted file mode 100644 index df5895afa74..00000000000 --- a/libgo/go/net/dialgoogle_test.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package net - -import ( - "flag" - "fmt" - "io" - "strings" - "syscall" - "testing" -) - -// If an IPv6 tunnel is running, we can try dialing a real IPv6 address. -var testIPv6 = flag.Bool("ipv6", false, "assume ipv6 tunnel is present") - -func TestResolveGoogle(t *testing.T) { - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } - - for _, network := range []string{"tcp", "tcp4", "tcp6"} { - addr, err := ResolveTCPAddr(network, "www.google.com:http") - if err != nil { - if (network == "tcp" || network == "tcp4") && !supportsIPv4 { - t.Logf("ipv4 is not supported: %v", err) - } else if network == "tcp6" && !supportsIPv6 { - t.Logf("ipv6 is not supported: %v", err) - } else { - t.Errorf("ResolveTCPAddr failed: %v", err) - } - continue - } - if (network == "tcp" || network == "tcp4") && addr.IP.To4() == nil { - t.Errorf("got %v; expected an IPv4 address on %v", addr, network) - } else if network == "tcp6" && (addr.IP.To16() == nil || addr.IP.To4() != nil) { - t.Errorf("got %v; expected an IPv6 address on %v", addr, network) - } - } -} - -func TestDialGoogle(t *testing.T) { - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } - - d := &Dialer{DualStack: true} - for _, network := range []string{"tcp", "tcp4", "tcp6"} { - if network == "tcp" && !supportsIPv4 && !supportsIPv6 { - t.Logf("skipping test; both ipv4 and ipv6 are not supported") - continue - } else if network == "tcp4" && !supportsIPv4 { - t.Logf("skipping test; ipv4 is not supported") - continue - } else if network == "tcp6" && !supportsIPv6 { - t.Logf("skipping test; ipv6 is not supported") - continue - } else if network == "tcp6" && !*testIPv6 { - t.Logf("test disabled; use -ipv6 to enable") - continue - } - if c, err := d.Dial(network, "www.google.com:http"); err != nil { - t.Errorf("Dial failed: %v", err) - } else { - c.Close() - } - } -} - -// fd is already connected to the destination, port 80. -// Run an HTTP request to fetch the appropriate page. -func fetchGoogle(t *testing.T, fd Conn, network, addr string) { - req := []byte("GET /robots.txt HTTP/1.0\r\nHost: www.google.com\r\n\r\n") - n, err := fd.Write(req) - - buf := make([]byte, 1000) - n, err = io.ReadFull(fd, buf) - - if n < 1000 { - t.Errorf("fetchGoogle: short HTTP read from %s %s - %v", network, addr, err) - return - } -} - -func doDial(t *testing.T, network, addr string) { - fd, err := Dial(network, addr) - if err != nil { - t.Errorf("Dial(%q, %q, %q) = _, %v", network, "", addr, err) - return - } - fetchGoogle(t, fd, network, addr) - fd.Close() -} - -var googleaddrsipv4 = []string{ - "%d.%d.%d.%d:80", - "www.google.com:80", - "%d.%d.%d.%d:http", - "www.google.com:http", - "%03d.%03d.%03d.%03d:0080", - "[::ffff:%d.%d.%d.%d]:80", - "[::ffff:%02x%02x:%02x%02x]:80", - "[0:0:0:0:0000:ffff:%d.%d.%d.%d]:80", - "[0:0:0:0:000000:ffff:%d.%d.%d.%d]:80", - "[0:0:0:0::ffff:%d.%d.%d.%d]:80", -} - -func TestDialGoogleIPv4(t *testing.T) { - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } - - // Insert an actual IPv4 address for google.com - // into the table. - addrs, err := LookupIP("www.google.com") - if err != nil { - t.Fatalf("lookup www.google.com: %v", err) - } - var ip IP - for _, addr := range addrs { - if x := addr.To4(); x != nil { - ip = x - break - } - } - if ip == nil { - t.Fatalf("no IPv4 addresses for www.google.com") - } - - for i, s := range googleaddrsipv4 { - if strings.Contains(s, "%") { - googleaddrsipv4[i] = fmt.Sprintf(s, ip[0], ip[1], ip[2], ip[3]) - } - } - - for i := 0; i < len(googleaddrsipv4); i++ { - addr := googleaddrsipv4[i] - if addr == "" { - continue - } - t.Logf("-- %s --", addr) - doDial(t, "tcp", addr) - if addr[0] != '[' { - doDial(t, "tcp4", addr) - if supportsIPv6 { - // make sure syscall.SocketDisableIPv6 flag works. - syscall.SocketDisableIPv6 = true - doDial(t, "tcp", addr) - doDial(t, "tcp4", addr) - syscall.SocketDisableIPv6 = false - } - } - } -} - -var googleaddrsipv6 = []string{ - "[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:80", - "ipv6.google.com:80", - "[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:http", - "ipv6.google.com:http", -} - -func TestDialGoogleIPv6(t *testing.T) { - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } - // Only run tcp6 if the kernel will take it. - if !supportsIPv6 { - t.Skip("skipping test; ipv6 is not supported") - } - if !*testIPv6 { - t.Skip("test disabled; use -ipv6 to enable") - } - - // Insert an actual IPv6 address for ipv6.google.com - // into the table. - addrs, err := LookupIP("ipv6.google.com") - if err != nil { - t.Fatalf("lookup ipv6.google.com: %v", err) - } - var ip IP - for _, addr := range addrs { - if x := addr.To16(); x != nil { - ip = x - break - } - } - if ip == nil { - t.Fatalf("no IPv6 addresses for ipv6.google.com") - } - - for i, s := range googleaddrsipv6 { - if strings.Contains(s, "%") { - googleaddrsipv6[i] = fmt.Sprintf(s, ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]) - } - } - - for i := 0; i < len(googleaddrsipv6); i++ { - addr := googleaddrsipv6[i] - if addr == "" { - continue - } - t.Logf("-- %s --", addr) - doDial(t, "tcp", addr) - doDial(t, "tcp6", addr) - } -} diff --git a/libgo/go/net/dnsclient.go b/libgo/go/net/dnsclient.go index e8014e4ffc9..ce48521bc60 100644 --- a/libgo/go/net/dnsclient.go +++ b/libgo/go/net/dnsclient.go @@ -9,31 +9,6 @@ import ( "sort" ) -// DNSError represents a DNS lookup error. -type DNSError struct { - Err string // description of the error - Name string // name looked for - Server string // server used - IsTimeout bool -} - -func (e *DNSError) Error() string { - if e == nil { - return "<nil>" - } - s := "lookup " + e.Name - if e.Server != "" { - s += " on " + e.Server - } - s += ": " + e.Err - return s -} - -func (e *DNSError) Timeout() bool { return e.IsTimeout } -func (e *DNSError) Temporary() bool { return e.IsTimeout } - -const noSuchHost = "no such host" - // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP // address addr suitable for rDNS (PTR) record lookup or an error if it fails // to parse the IP address. @@ -43,8 +18,7 @@ func reverseaddr(addr string) (arpa string, err error) { return "", &DNSError{Err: "unrecognized address", Name: addr} } if ip.To4() != nil { - return itoa(int(ip[15])) + "." + itoa(int(ip[14])) + "." + itoa(int(ip[13])) + "." + - itoa(int(ip[12])) + ".in-addr.arpa.", nil + return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa.", nil } // Must be IPv6 buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) @@ -67,7 +41,7 @@ func answer(name, server string, dns *dnsMsg, qtype uint16) (cname string, addrs addrs = make([]dnsRR, 0, len(dns.answer)) if dns.rcode == dnsRcodeNameError && dns.recursion_available { - return "", nil, &DNSError{Err: noSuchHost, Name: name} + return "", nil, &DNSError{Err: errNoSuchHost.Error(), Name: name, Server: server} } if dns.rcode != dnsRcodeSuccess { // None of the error codes make sense @@ -94,7 +68,7 @@ Cname: continue } h := rr.Header() - if h.Class == dnsClassINET && h.Name == name { + if h.Class == dnsClassINET && equalASCIILabel(h.Name, name) { switch h.Rrtype { case qtype: addrs = append(addrs, rr) @@ -106,7 +80,7 @@ Cname: } } if len(addrs) == 0 { - return "", nil, &DNSError{Err: noSuchHost, Name: name, Server: server} + return "", nil, &DNSError{Err: errNoSuchHost.Error(), Name: name, Server: server} } return name, addrs, nil } @@ -114,6 +88,26 @@ Cname: return "", nil, &DNSError{Err: "too many redirects", Name: name, Server: server} } +func equalASCIILabel(x, y string) bool { + if len(x) != len(y) { + return false + } + for i := 0; i < len(x); i++ { + a := x[i] + b := y[i] + if 'A' <= a && a <= 'Z' { + a += 0x20 + } + if 'A' <= b && b <= 'Z' { + b += 0x20 + } + if a != b { + return false + } + } + return true +} + func isDomainName(s string) bool { // See RFC 1035, RFC 3696. if len(s) == 0 { @@ -174,13 +168,10 @@ type SRV struct { type byPriorityWeight []*SRV func (s byPriorityWeight) Len() int { return len(s) } - -func (s byPriorityWeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - func (s byPriorityWeight) Less(i, j int) bool { - return s[i].Priority < s[j].Priority || - (s[i].Priority == s[j].Priority && s[i].Weight < s[j].Weight) + return s[i].Priority < s[j].Priority || (s[i].Priority == s[j].Priority && s[i].Weight < s[j].Weight) } +func (s byPriorityWeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // shuffleByWeight shuffles SRV records by weight using the algorithm // described in RFC 2782. @@ -228,11 +219,9 @@ type MX struct { // byPref implements sort.Interface to sort MX records by preference type byPref []*MX -func (s byPref) Len() int { return len(s) } - +func (s byPref) Len() int { return len(s) } func (s byPref) Less(i, j int) bool { return s[i].Pref < s[j].Pref } - -func (s byPref) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byPref) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // sort reorders MX records as specified in RFC 5321. func (s byPref) sort() { diff --git a/libgo/go/net/dnsclient_test.go b/libgo/go/net/dnsclient_test.go index 435eb35506e..3ab2b836ef6 100644 --- a/libgo/go/net/dnsclient_test.go +++ b/libgo/go/net/dnsclient_test.go @@ -47,7 +47,7 @@ func testUniformity(t *testing.T, size int, margin float64) { checkDistribution(t, data, margin) } -func TestUniformity(t *testing.T) { +func TestDNSSRVUniformity(t *testing.T) { testUniformity(t, 2, 0.05) testUniformity(t, 3, 0.10) testUniformity(t, 10, 0.20) diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go index 7511083f795..c03c1b1159f 100644 --- a/libgo/go/net/dnsclient_unix.go +++ b/libgo/go/net/dnsclient_unix.go @@ -20,6 +20,7 @@ import ( "io" "math/rand" "os" + "strconv" "sync" "time" ) @@ -184,9 +185,9 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, err } continue } - cname, addrs, err := answer(name, server, msg, qtype) - if err == nil || err.(*DNSError).Err == noSuchHost { - return cname, addrs, err + cname, rrs, err := answer(name, server, msg, qtype) + if err == nil || msg.rcode == dnsRcodeSuccess || msg.rcode == dnsRcodeNameError && msg.recursion_available { + return cname, rrs, err } lastErr = err } @@ -194,144 +195,188 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, err return "", nil, lastErr } -func convertRR_A(records []dnsRR) []IP { - addrs := make([]IP, len(records)) - for i, rr := range records { - a := rr.(*dnsRR_A).A - addrs[i] = IPv4(byte(a>>24), byte(a>>16), byte(a>>8), byte(a)) +// addrRecordList converts and returns a list of IP addresses from DNS +// address records (both A and AAAA). Other record types are ignored. +func addrRecordList(rrs []dnsRR) []IPAddr { + addrs := make([]IPAddr, 0, 4) + for _, rr := range rrs { + switch rr := rr.(type) { + case *dnsRR_A: + addrs = append(addrs, IPAddr{IP: IPv4(byte(rr.A>>24), byte(rr.A>>16), byte(rr.A>>8), byte(rr.A))}) + case *dnsRR_AAAA: + ip := make(IP, IPv6len) + copy(ip, rr.AAAA[:]) + addrs = append(addrs, IPAddr{IP: ip}) + } } return addrs } -func convertRR_AAAA(records []dnsRR) []IP { - addrs := make([]IP, len(records)) - for i, rr := range records { - a := make(IP, IPv6len) - copy(a, rr.(*dnsRR_AAAA).AAAA[:]) - addrs[i] = a - } - return addrs -} +// A resolverConfig represents a DNS stub resolver configuration. +type resolverConfig struct { + initOnce sync.Once // guards init of resolverConfig -var cfg struct { - ch chan struct{} - mu sync.RWMutex // protects dnsConfig and dnserr - dnsConfig *dnsConfig - dnserr error -} -var onceLoadConfig sync.Once + // ch is used as a semaphore that only allows one lookup at a + // time to recheck resolv.conf. + ch chan struct{} // guards lastChecked and modTime + lastChecked time.Time // last time resolv.conf was checked + modTime time.Time // time of resolv.conf modification -// Assume dns config file is /etc/resolv.conf here -func loadDefaultConfig() { - loadConfig("/etc/resolv.conf", 5*time.Second, nil) + mu sync.RWMutex // protects dnsConfig + dnsConfig *dnsConfig // parsed resolv.conf structure used in lookups } -func loadConfig(resolvConfPath string, reloadTime time.Duration, quit <-chan chan struct{}) { - var mtime time.Time - cfg.ch = make(chan struct{}, 1) - if fi, err := os.Stat(resolvConfPath); err != nil { - cfg.dnserr = err - } else { - mtime = fi.ModTime() - cfg.dnsConfig, cfg.dnserr = dnsReadConfig(resolvConfPath) - } - go func() { - for { - time.Sleep(reloadTime) - select { - case qresp := <-quit: - qresp <- struct{}{} - return - case <-cfg.ch: - } +var resolvConf resolverConfig - // In case of error, we keep the previous config - fi, err := os.Stat(resolvConfPath) - if err != nil { - continue - } - // If the resolv.conf mtime didn't change, do not reload - m := fi.ModTime() - if m.Equal(mtime) { - continue - } - mtime = m - // In case of error, we keep the previous config - ncfg, err := dnsReadConfig(resolvConfPath) - if err != nil || len(ncfg.servers) == 0 { - continue - } - cfg.mu.Lock() - cfg.dnsConfig = ncfg - cfg.dnserr = nil - cfg.mu.Unlock() - } - }() -} - -func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) { - if !isDomainName(name) { - return name, nil, &DNSError{Err: "invalid domain name", Name: name} +// init initializes conf and is only called via conf.initOnce. +func (conf *resolverConfig) init() { + // Set dnsConfig, modTime, and lastChecked so we don't parse + // resolv.conf twice the first time. + conf.dnsConfig = systemConf().resolv + if conf.dnsConfig == nil { + conf.dnsConfig = dnsReadConfig("/etc/resolv.conf") } - onceLoadConfig.Do(loadDefaultConfig) - select { - case cfg.ch <- struct{}{}: - default: + if fi, err := os.Stat("/etc/resolv.conf"); err == nil { + conf.modTime = fi.ModTime() } + conf.lastChecked = time.Now() + + // Prepare ch so that only one update of resolverConfig may + // run at once. + conf.ch = make(chan struct{}, 1) +} - cfg.mu.RLock() - defer cfg.mu.RUnlock() +// tryUpdate tries to update conf with the named resolv.conf file. +// The name variable only exists for testing. It is otherwise always +// "/etc/resolv.conf". +func (conf *resolverConfig) tryUpdate(name string) { + conf.initOnce.Do(conf.init) - if cfg.dnserr != nil || cfg.dnsConfig == nil { - err = cfg.dnserr + // Ensure only one update at a time checks resolv.conf. + if !conf.tryAcquireSema() { return } - // If name is rooted (trailing dot) or has enough dots, - // try it by itself first. - rooted := len(name) > 0 && name[len(name)-1] == '.' - if rooted || count(name, '.') >= cfg.dnsConfig.ndots { - rname := name - if !rooted { - rname += "." - } - // Can try as ordinary name. - cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) - if rooted || err == nil { - return - } + defer conf.releaseSema() + + now := time.Now() + if conf.lastChecked.After(now.Add(-5 * time.Second)) { + return } + conf.lastChecked = now - // Otherwise, try suffixes. - for i := 0; i < len(cfg.dnsConfig.search); i++ { - rname := name + "." + cfg.dnsConfig.search[i] - if rname[len(rname)-1] != '.' { - rname += "." + if fi, err := os.Stat(name); err == nil { + if fi.ModTime().Equal(conf.modTime) { + return } - cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) - if err == nil { + conf.modTime = fi.ModTime() + } else { + // If modTime wasn't set prior, assume nothing has changed. + if conf.modTime.IsZero() { return } + conf.modTime = time.Time{} } - // Last ditch effort: try unsuffixed only if we haven't already, - // that is, name is not rooted and has less than ndots dots. - if count(name, '.') < cfg.dnsConfig.ndots { - cname, addrs, err = tryOneName(cfg.dnsConfig, name+".", qtype) + dnsConf := dnsReadConfig(name) + conf.mu.Lock() + conf.dnsConfig = dnsConf + conf.mu.Unlock() +} + +func (conf *resolverConfig) tryAcquireSema() bool { + select { + case conf.ch <- struct{}{}: + return true + default: + return false + } +} + +func (conf *resolverConfig) releaseSema() { + <-conf.ch +} + +func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) { + if !isDomainName(name) { + return "", nil, &DNSError{Err: "invalid domain name", Name: name} + } + resolvConf.tryUpdate("/etc/resolv.conf") + resolvConf.mu.RLock() + conf := resolvConf.dnsConfig + resolvConf.mu.RUnlock() + for _, fqdn := range conf.nameList(name) { + cname, rrs, err = tryOneName(conf, fqdn, qtype) if err == nil { - return + break } } - - if e, ok := err.(*DNSError); ok { + if err, ok := err.(*DNSError); ok { // Show original name passed to lookup, not suffixed one. // In general we might have tried many suffixes; showing // just one is misleading. See also golang.org/issue/6324. - e.Name = name + err.Name = name } return } +// nameList returns a list of names for sequential DNS queries. +func (conf *dnsConfig) nameList(name string) []string { + // If name is rooted (trailing dot), try only that name. + rooted := len(name) > 0 && name[len(name)-1] == '.' + if rooted { + return []string{name} + } + // Build list of search choices. + names := make([]string, 0, 1+len(conf.search)) + // If name has enough dots, try unsuffixed first. + if count(name, '.') >= conf.ndots { + names = append(names, name+".") + } + // Try suffixes. + for _, suffix := range conf.search { + suffixed := name + "." + suffix + if suffixed[len(suffixed)-1] != '.' { + suffixed += "." + } + names = append(names, suffixed) + } + // Try unsuffixed, if not tried first above. + if count(name, '.') < conf.ndots { + names = append(names, name+".") + } + return names +} + +// hostLookupOrder specifies the order of LookupHost lookup strategies. +// It is basically a simplified representation of nsswitch.conf. +// "files" means /etc/hosts. +type hostLookupOrder int + +const ( + // hostLookupCgo means defer to cgo. + hostLookupCgo hostLookupOrder = iota + hostLookupFilesDNS // files first + hostLookupDNSFiles // dns first + hostLookupFiles // only files + hostLookupDNS // only DNS +) + +var lookupOrderName = map[hostLookupOrder]string{ + hostLookupCgo: "cgo", + hostLookupFilesDNS: "files,dns", + hostLookupDNSFiles: "dns,files", + hostLookupFiles: "files", + hostLookupDNS: "dns", +} + +func (o hostLookupOrder) String() string { + if s, ok := lookupOrderName[o]; ok { + return s + } + return "hostLookupOrder=" + strconv.Itoa(int(o)) + "??" +} + // goLookupHost is the native Go implementation of LookupHost. // Used only if cgoLookupHost refuses to handle the request // (that is, only if cgoLookupHost is the stub in cgo_stub.go). @@ -339,12 +384,18 @@ func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) // depending on our lookup code, so that Go and C get the same // answers. func goLookupHost(name string) (addrs []string, err error) { - // Use entries from /etc/hosts if they match. - addrs = lookupStaticHost(name) - if len(addrs) > 0 { - return + return goLookupHostOrder(name, hostLookupFilesDNS) +} + +func goLookupHostOrder(name string, order hostLookupOrder) (addrs []string, err error) { + if order == hostLookupFilesDNS || order == hostLookupFiles { + // Use entries from /etc/hosts if they match. + addrs = lookupStaticHost(name) + if len(addrs) > 0 || order == hostLookupFiles { + return + } } - ips, err := goLookupIP(name) + ips, err := goLookupIPOrder(name, order) if err != nil { return } @@ -355,54 +406,79 @@ func goLookupHost(name string) (addrs []string, err error) { return } -// goLookupIP is the native Go implementation of LookupIP. -// Used only if cgoLookupIP refuses to handle the request -// (that is, only if cgoLookupIP is the stub in cgo_stub.go). -// Normally we let cgo use the C library resolver instead of -// depending on our lookup code, so that Go and C get the same -// answers. -func goLookupIP(name string) (addrs []IP, err error) { - // Use entries from /etc/hosts if possible. - haddrs := lookupStaticHost(name) - if len(haddrs) > 0 { - for _, haddr := range haddrs { - if ip := ParseIP(haddr); ip != nil { - addrs = append(addrs, ip) - } +// lookup entries from /etc/hosts +func goLookupIPFiles(name string) (addrs []IPAddr) { + for _, haddr := range lookupStaticHost(name) { + haddr, zone := splitHostZone(haddr) + if ip := ParseIP(haddr); ip != nil { + addr := IPAddr{IP: ip, Zone: zone} + addrs = append(addrs, addr) } - if len(addrs) > 0 { - return + } + sortByRFC6724(addrs) + return +} + +// goLookupIP is the native Go implementation of LookupIP. +// The libc versions are in cgo_*.go. +func goLookupIP(name string) (addrs []IPAddr, err error) { + return goLookupIPOrder(name, hostLookupFilesDNS) +} + +func goLookupIPOrder(name string, order hostLookupOrder) (addrs []IPAddr, err error) { + if order == hostLookupFilesDNS || order == hostLookupFiles { + addrs = goLookupIPFiles(name) + if len(addrs) > 0 || order == hostLookupFiles { + return addrs, nil } } + if !isDomainName(name) { + return nil, &DNSError{Err: "invalid domain name", Name: name} + } + resolvConf.tryUpdate("/etc/resolv.conf") + resolvConf.mu.RLock() + conf := resolvConf.dnsConfig + resolvConf.mu.RUnlock() type racer struct { - qtype uint16 - rrs []dnsRR + rrs []dnsRR error } lane := make(chan racer, 1) qtypes := [...]uint16{dnsTypeA, dnsTypeAAAA} - for _, qtype := range qtypes { - go func(qtype uint16) { - _, rrs, err := lookup(name, qtype) - lane <- racer{qtype, rrs, err} - }(qtype) - } var lastErr error - for range qtypes { - racer := <-lane - if racer.error != nil { - lastErr = racer.error - continue + for _, fqdn := range conf.nameList(name) { + for _, qtype := range qtypes { + go func(qtype uint16) { + _, rrs, err := tryOneName(conf, fqdn, qtype) + lane <- racer{rrs, err} + }(qtype) + } + for range qtypes { + racer := <-lane + if racer.error != nil { + lastErr = racer.error + continue + } + addrs = append(addrs, addrRecordList(racer.rrs)...) } - switch racer.qtype { - case dnsTypeA: - addrs = append(addrs, convertRR_A(racer.rrs)...) - case dnsTypeAAAA: - addrs = append(addrs, convertRR_AAAA(racer.rrs)...) + if len(addrs) > 0 { + break } } - if len(addrs) == 0 && lastErr != nil { - return nil, lastErr + if lastErr, ok := lastErr.(*DNSError); ok { + // Show original name passed to lookup, not suffixed one. + // In general we might have tried many suffixes; showing + // just one is misleading. See also golang.org/issue/6324. + lastErr.Name = name + } + sortByRFC6724(addrs) + if len(addrs) == 0 { + if lastErr != nil { + return nil, lastErr + } + if order == hostLookupDNSFiles { + addrs = goLookupIPFiles(name) + } } return addrs, nil } @@ -414,10 +490,35 @@ func goLookupIP(name string) (addrs []IP, err error) { // depending on our lookup code, so that Go and C get the same // answers. func goLookupCNAME(name string) (cname string, err error) { - _, rr, err := lookup(name, dnsTypeCNAME) + _, rrs, err := lookup(name, dnsTypeCNAME) if err != nil { return } - cname = rr[0].(*dnsRR_CNAME).Cname + cname = rrs[0].(*dnsRR_CNAME).Cname return } + +// goLookupPTR is the native Go implementation of LookupAddr. +// Used only if cgoLookupPTR refuses to handle the request (that is, +// only if cgoLookupPTR is the stub in cgo_stub.go). +// Normally we let cgo use the C library resolver instead of depending +// on our lookup code, so that Go and C get the same answers. +func goLookupPTR(addr string) ([]string, error) { + names := lookupStaticAddr(addr) + if len(names) > 0 { + return names, nil + } + arpa, err := reverseaddr(addr) + if err != nil { + return nil, err + } + _, rrs, err := lookup(arpa, dnsTypePTR) + if err != nil { + return nil, err + } + ptrs := make([]string, len(rrs)) + for i, rr := range rrs { + ptrs[i] = rr.(*dnsRR_PTR).Ptr + } + return ptrs, nil +} diff --git a/libgo/go/net/dnsclient_unix_test.go b/libgo/go/net/dnsclient_unix_test.go index 1167c26b39d..a999f8f0607 100644 --- a/libgo/go/net/dnsclient_unix_test.go +++ b/libgo/go/net/dnsclient_unix_test.go @@ -7,11 +7,13 @@ package net import ( - "io" + "fmt" "io/ioutil" "os" "path" "reflect" + "strings" + "sync" "testing" "time" ) @@ -31,7 +33,7 @@ var dnsTransportFallbackTests = []struct { func TestDNSTransportFallback(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } for _, tt := range dnsTransportFallbackTests { @@ -57,13 +59,13 @@ var specialDomainNameTests = []struct { qtype uint16 rcode int }{ - // Name resoltion APIs and libraries should not recongnize the + // Name resolution APIs and libraries should not recognize the // followings as special. {"1.0.168.192.in-addr.arpa.", dnsTypePTR, dnsRcodeNameError}, {"test.", dnsTypeALL, dnsRcodeNameError}, {"example.com.", dnsTypeALL, dnsRcodeSuccess}, - // Name resoltion APIs and libraries should recongnize the + // Name resolution APIs and libraries should recognize the // followings as special and should not send any queries. // Though, we test those names here for verifying nagative // answers at DNS query-response interaction level. @@ -73,7 +75,7 @@ var specialDomainNameTests = []struct { func TestSpecialDomainName(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } server := "8.8.8.8:53" @@ -93,154 +95,323 @@ func TestSpecialDomainName(t *testing.T) { } type resolvConfTest struct { - *testing.T - dir string - path string - started bool - quitc chan chan struct{} + dir string + path string + *resolverConfig } -func newResolvConfTest(t *testing.T) *resolvConfTest { - dir, err := ioutil.TempDir("", "resolvConfTest") +func newResolvConfTest() (*resolvConfTest, error) { + dir, err := ioutil.TempDir("", "go-resolvconftest") if err != nil { - t.Fatalf("could not create temp dir: %v", err) + return nil, err } - - // Disable the default loadConfig - onceLoadConfig.Do(func() {}) - - r := &resolvConfTest{ - T: t, - dir: dir, - path: path.Join(dir, "resolv.conf"), - quitc: make(chan chan struct{}), + conf := &resolvConfTest{ + dir: dir, + path: path.Join(dir, "resolv.conf"), + resolverConfig: &resolvConf, } - - return r + conf.initOnce.Do(conf.init) + return conf, nil } -func (r *resolvConfTest) Start() { - loadConfig(r.path, 100*time.Millisecond, r.quitc) - r.started = true -} - -func (r *resolvConfTest) SetConf(s string) { - // Make sure the file mtime will be different once we're done here, - // even on systems with coarse (1s) mtime resolution. - time.Sleep(time.Second) - - f, err := os.OpenFile(r.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) +func (conf *resolvConfTest) writeAndUpdate(lines []string) error { + f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - r.Fatalf("failed to create temp file %s: %v", r.path, err) + return err } - if _, err := io.WriteString(f, s); err != nil { + if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil { f.Close() - r.Fatalf("failed to write temp file: %v", err) + return err } f.Close() - - if r.started { - cfg.ch <- struct{}{} // fill buffer - cfg.ch <- struct{}{} // wait for reload to begin - cfg.ch <- struct{}{} // wait for reload to complete + if err := conf.forceUpdate(conf.path); err != nil { + return err } + return nil } -func (r *resolvConfTest) WantServers(want []string) { - cfg.mu.RLock() - defer cfg.mu.RUnlock() - if got := cfg.dnsConfig.servers; !reflect.DeepEqual(got, want) { - r.Fatalf("Unexpected dns server loaded, got %v want %v", got, want) +func (conf *resolvConfTest) forceUpdate(name string) error { + dnsConf := dnsReadConfig(name) + conf.mu.Lock() + conf.dnsConfig = dnsConf + conf.mu.Unlock() + for i := 0; i < 5; i++ { + if conf.tryAcquireSema() { + conf.lastChecked = time.Time{} + conf.releaseSema() + return nil + } } + return fmt.Errorf("tryAcquireSema for %s failed", name) } -func (r *resolvConfTest) Close() { - resp := make(chan struct{}) - r.quitc <- resp - <-resp - if err := os.RemoveAll(r.dir); err != nil { - r.Logf("failed to remove temp dir %s: %v", r.dir, err) - } +func (conf *resolvConfTest) servers() []string { + conf.mu.RLock() + servers := conf.dnsConfig.servers + conf.mu.RUnlock() + return servers } -func TestReloadResolvConfFail(t *testing.T) { - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } +func (conf *resolvConfTest) teardown() error { + err := conf.forceUpdate("/etc/resolv.conf") + os.RemoveAll(conf.dir) + return err +} - r := newResolvConfTest(t) - defer r.Close() +var updateResolvConfTests = []struct { + name string // query name + lines []string // resolver configuration lines + servers []string // expected name servers +}{ + { + name: "golang.org", + lines: []string{"nameserver 8.8.8.8"}, + servers: []string{"8.8.8.8"}, + }, + { + name: "", + lines: nil, // an empty resolv.conf should use defaultNS as name servers + servers: defaultNS, + }, + { + name: "www.example.com", + lines: []string{"nameserver 8.8.4.4"}, + servers: []string{"8.8.4.4"}, + }, +} - // resolv.conf.tmp does not exist yet - r.Start() - if _, err := goLookupIP("golang.org"); err == nil { - t.Fatal("goLookupIP(missing) succeeded") +func TestUpdateResolvConf(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("avoid external network") } - r.SetConf("nameserver 8.8.8.8") - if _, err := goLookupIP("golang.org"); err != nil { - t.Fatalf("goLookupIP(missing; good) failed: %v", err) + conf, err := newResolvConfTest() + if err != nil { + t.Fatal(err) } + defer conf.teardown() - // Using a bad resolv.conf while we had a good - // one before should not update the config - r.SetConf("") - if _, err := goLookupIP("golang.org"); err != nil { - t.Fatalf("goLookupIP(missing; good; bad) failed: %v", err) + for i, tt := range updateResolvConfTests { + if err := conf.writeAndUpdate(tt.lines); err != nil { + t.Error(err) + continue + } + if tt.name != "" { + var wg sync.WaitGroup + const N = 10 + wg.Add(N) + for j := 0; j < N; j++ { + go func(name string) { + defer wg.Done() + ips, err := goLookupIP(name) + if err != nil { + t.Error(err) + return + } + if len(ips) == 0 { + t.Errorf("no records for %s", name) + return + } + }(tt.name) + } + wg.Wait() + } + servers := conf.servers() + if !reflect.DeepEqual(servers, tt.servers) { + t.Errorf("#%d: got %v; want %v", i, servers, tt.servers) + continue + } } } -func TestReloadResolvConfChange(t *testing.T) { +var goLookupIPWithResolverConfigTests = []struct { + name string + lines []string // resolver configuration lines + error + a, aaaa bool // whether response contains A, AAAA-record +}{ + // no records, transport timeout + { + "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", + []string{ + "options timeout:1 attempts:1", + "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address + }, + &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true}, + false, false, + }, + + // no records, non-existent domain + { + "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", + []string{ + "options timeout:3 attempts:1", + "nameserver 8.8.8.8", + }, + &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false}, + false, false, + }, + + // a few A records, no AAAA records + { + "ipv4.google.com.", + []string{ + "nameserver 8.8.8.8", + "nameserver 2001:4860:4860::8888", + }, + nil, + true, false, + }, + { + "ipv4.google.com", + []string{ + "domain golang.org", + "nameserver 2001:4860:4860::8888", + "nameserver 8.8.8.8", + }, + nil, + true, false, + }, + { + "ipv4.google.com", + []string{ + "search x.golang.org y.golang.org", + "nameserver 2001:4860:4860::8888", + "nameserver 8.8.8.8", + }, + nil, + true, false, + }, + + // no A records, a few AAAA records + { + "ipv6.google.com.", + []string{ + "nameserver 2001:4860:4860::8888", + "nameserver 8.8.8.8", + }, + nil, + false, true, + }, + { + "ipv6.google.com", + []string{ + "domain golang.org", + "nameserver 8.8.8.8", + "nameserver 2001:4860:4860::8888", + }, + nil, + false, true, + }, + { + "ipv6.google.com", + []string{ + "search x.golang.org y.golang.org", + "nameserver 8.8.8.8", + "nameserver 2001:4860:4860::8888", + }, + nil, + false, true, + }, + + // both A and AAAA records + { + "hostname.as112.net", // see RFC 7534 + []string{ + "domain golang.org", + "nameserver 2001:4860:4860::8888", + "nameserver 8.8.8.8", + }, + nil, + true, true, + }, + { + "hostname.as112.net", // see RFC 7534 + []string{ + "search x.golang.org y.golang.org", + "nameserver 2001:4860:4860::8888", + "nameserver 8.8.8.8", + }, + nil, + true, true, + }, +} + +func TestGoLookupIPWithResolverConfig(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - r := newResolvConfTest(t) - defer r.Close() - - r.SetConf("nameserver 8.8.8.8") - r.Start() - - if _, err := goLookupIP("golang.org"); err != nil { - t.Fatalf("goLookupIP(good) failed: %v", err) + conf, err := newResolvConfTest() + if err != nil { + t.Fatal(err) } - r.WantServers([]string{"8.8.8.8"}) + defer conf.teardown() - // Using a bad resolv.conf when we had a good one - // before should not update the config - r.SetConf("") - if _, err := goLookupIP("golang.org"); err != nil { - t.Fatalf("goLookupIP(good; bad) failed: %v", err) + for _, tt := range goLookupIPWithResolverConfigTests { + if err := conf.writeAndUpdate(tt.lines); err != nil { + t.Error(err) + continue + } + conf.tryUpdate(conf.path) + addrs, err := goLookupIP(tt.name) + if err != nil { + if err, ok := err.(*DNSError); !ok || (err.Name != tt.error.(*DNSError).Name || err.Server != tt.error.(*DNSError).Server || err.IsTimeout != tt.error.(*DNSError).IsTimeout) { + t.Errorf("got %v; want %v", err, tt.error) + } + continue + } + if len(addrs) == 0 { + t.Errorf("no records for %s", tt.name) + } + if !tt.a && !tt.aaaa && len(addrs) > 0 { + t.Errorf("unexpected %v for %s", addrs, tt.name) + } + for _, addr := range addrs { + if !tt.a && addr.IP.To4() != nil { + t.Errorf("got %v; must not be IPv4 address", addr) + } + if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil { + t.Errorf("got %v; must not be IPv6 address", addr) + } + } } - - // A new good config should get picked up - r.SetConf("nameserver 8.8.4.4") - r.WantServers([]string{"8.8.4.4"}) } func BenchmarkGoLookupIP(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { goLookupIP("www.example.com") } } func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { goLookupIP("some.nonexistent") } } func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { - onceLoadConfig.Do(loadDefaultConfig) - if cfg.dnserr != nil || cfg.dnsConfig == nil { - b.Fatalf("loadConfig failed: %v", cfg.dnserr) - } - // This looks ugly but it's safe as long as benchmarks are run - // sequentially in package testing. - orig := cfg.dnsConfig - cfg.dnsConfig.servers = append([]string{"203.0.113.254"}, cfg.dnsConfig.servers...) // use TEST-NET-3 block, see RFC 5737 + testHookUninstaller.Do(uninstallTestHooks) + + conf, err := newResolvConfTest() + if err != nil { + b.Fatal(err) + } + defer conf.teardown() + + lines := []string{ + "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737 + "nameserver 8.8.8.8", + } + if err := conf.writeAndUpdate(lines); err != nil { + b.Fatal(err) + } + for i := 0; i < b.N; i++ { goLookupIP("www.example.com") } - cfg.dnsConfig = orig } diff --git a/libgo/go/net/dnsconfig_unix.go b/libgo/go/net/dnsconfig_unix.go index 66ab7c4dd30..6073fdb6d83 100644 --- a/libgo/go/net/dnsconfig_unix.go +++ b/libgo/go/net/dnsconfig_unix.go @@ -8,30 +8,41 @@ package net +var defaultNS = []string{"127.0.0.1", "::1"} + type dnsConfig struct { - servers []string // servers to use - search []string // suffixes to append to local name - ndots int // number of dots in name to trigger absolute lookup - timeout int // seconds before giving up on packet - attempts int // lost packets before giving up on server - rotate bool // round robin among servers + servers []string // servers to use + search []string // suffixes to append to local name + ndots int // number of dots in name to trigger absolute lookup + timeout int // seconds before giving up on packet + attempts int // lost packets before giving up on server + rotate bool // round robin among servers + unknownOpt bool // anything unknown was encountered + lookup []string // OpenBSD top-level database "lookup" order + err error // any error that occurs during open of resolv.conf } // See resolv.conf(5) on a Linux machine. // TODO(rsc): Supposed to call uname() and chop the beginning // of the host name to get the default search domain. -func dnsReadConfig(filename string) (*dnsConfig, error) { - file, err := open(filename) - if err != nil { - return nil, &DNSConfigError{err} - } - defer file.close() +func dnsReadConfig(filename string) *dnsConfig { conf := &dnsConfig{ ndots: 1, timeout: 5, attempts: 2, } + file, err := open(filename) + if err != nil { + conf.servers = defaultNS + conf.err = err + return conf + } + defer file.close() for line, ok := file.readLine(); ok; line, ok = file.readLine() { + if len(line) > 0 && (line[0] == ';' || line[0] == '#') { + // comment. + continue + } f := getFields(line) if len(f) < 1 { continue @@ -61,8 +72,7 @@ func dnsReadConfig(filename string) (*dnsConfig, error) { } case "options": // magic options - for i := 1; i < len(f); i++ { - s := f[i] + for _, s := range f[1:] { switch { case hasPrefix(s, "ndots:"): n, _, _ := dtoi(s, 6) @@ -84,11 +94,25 @@ func dnsReadConfig(filename string) (*dnsConfig, error) { conf.attempts = n case s == "rotate": conf.rotate = true + default: + conf.unknownOpt = true } } + + case "lookup": + // OpenBSD option: + // http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5 + // "the legal space-separated values are: bind, file, yp" + conf.lookup = f[1:] + + default: + conf.unknownOpt = true } } - return conf, nil + if len(conf.servers) == 0 { + conf.servers = defaultNS + } + return conf } func hasPrefix(s, prefix string) bool { diff --git a/libgo/go/net/dnsconfig_unix_test.go b/libgo/go/net/dnsconfig_unix_test.go index 94fb0c32e24..c8eed618904 100644 --- a/libgo/go/net/dnsconfig_unix_test.go +++ b/libgo/go/net/dnsconfig_unix_test.go @@ -7,28 +7,30 @@ package net import ( + "os" "reflect" "testing" ) var dnsReadConfigTests = []struct { name string - conf dnsConfig + want *dnsConfig }{ { name: "testdata/resolv.conf", - conf: dnsConfig{ - servers: []string{"8.8.8.8", "2001:4860:4860::8888", "fe80::1%lo0"}, - search: []string{"localdomain"}, - ndots: 5, - timeout: 10, - attempts: 3, - rotate: true, + want: &dnsConfig{ + servers: []string{"8.8.8.8", "2001:4860:4860::8888", "fe80::1%lo0"}, + search: []string{"localdomain"}, + ndots: 5, + timeout: 10, + attempts: 3, + rotate: true, + unknownOpt: true, // the "options attempts 3" line }, }, { name: "testdata/domain-resolv.conf", - conf: dnsConfig{ + want: &dnsConfig{ servers: []string{"8.8.8.8"}, search: []string{"localdomain"}, ndots: 1, @@ -38,7 +40,7 @@ var dnsReadConfigTests = []struct { }, { name: "testdata/search-resolv.conf", - conf: dnsConfig{ + want: &dnsConfig{ servers: []string{"8.8.8.8"}, search: []string{"test", "invalid"}, ndots: 1, @@ -48,22 +50,51 @@ var dnsReadConfigTests = []struct { }, { name: "testdata/empty-resolv.conf", - conf: dnsConfig{ + want: &dnsConfig{ + servers: defaultNS, + ndots: 1, + timeout: 5, + attempts: 2, + }, + }, + { + name: "testdata/openbsd-resolv.conf", + want: &dnsConfig{ ndots: 1, timeout: 5, attempts: 2, + lookup: []string{"file", "bind"}, + servers: []string{"169.254.169.254", "10.240.0.1"}, + search: []string{"c.symbolic-datum-552.internal."}, }, }, } func TestDNSReadConfig(t *testing.T) { for _, tt := range dnsReadConfigTests { - conf, err := dnsReadConfig(tt.name) - if err != nil { - t.Fatal(err) + conf := dnsReadConfig(tt.name) + if conf.err != nil { + t.Fatal(conf.err) } - if !reflect.DeepEqual(conf, &tt.conf) { - t.Errorf("got %v; want %v", conf, &tt.conf) + if !reflect.DeepEqual(conf, tt.want) { + t.Errorf("%s:\ngot: %+v\nwant: %+v", tt.name, conf, tt.want) } } } + +func TestDNSReadMissingFile(t *testing.T) { + conf := dnsReadConfig("a-nonexistent-file") + if !os.IsNotExist(conf.err) { + t.Errorf("missing resolv.conf:\ngot: %v\nwant: %v", conf.err, os.ErrNotExist) + } + conf.err = nil + want := &dnsConfig{ + servers: defaultNS, + ndots: 1, + timeout: 5, + attempts: 2, + } + if !reflect.DeepEqual(conf, want) { + t.Errorf("missing resolv.conf:\ngot: %+v\nwant: %+v", conf, want) + } +} diff --git a/libgo/go/net/dnsmsg.go b/libgo/go/net/dnsmsg.go index 161afb2a556..6ecaa948230 100644 --- a/libgo/go/net/dnsmsg.go +++ b/libgo/go/net/dnsmsg.go @@ -306,7 +306,23 @@ func (rr *dnsRR_TXT) Header() *dnsRR_Header { } func (rr *dnsRR_TXT) Walk(f func(v interface{}, name, tag string) bool) bool { - return rr.Hdr.Walk(f) && f(&rr.Txt, "Txt", "") + if !rr.Hdr.Walk(f) { + return false + } + var n uint16 = 0 + for n < rr.Hdr.Rdlength { + var txt string + if !f(&txt, "Txt", "") { + return false + } + // more bytes than rr.Hdr.Rdlength said there woudld be + if rr.Hdr.Rdlength-n < uint16(len(txt))+1 { + return false + } + n += uint16(len(txt)) + 1 + rr.Txt += txt + } + return true } type dnsRR_SRV struct { diff --git a/libgo/go/net/dnsmsg_test.go b/libgo/go/net/dnsmsg_test.go index c39dbdb049d..1078d77ceb9 100644 --- a/libgo/go/net/dnsmsg_test.go +++ b/libgo/go/net/dnsmsg_test.go @@ -18,7 +18,7 @@ func TestDNSParseSRVReply(t *testing.T) { msg := new(dnsMsg) ok := msg.Unpack(data) if !ok { - t.Fatalf("unpacking packet failed") + t.Fatal("unpacking packet failed") } msg.String() // exercise this code path if g, e := len(msg.answer), 5; g != e { @@ -32,13 +32,19 @@ func TestDNSParseSRVReply(t *testing.T) { t.Errorf("answer[%d] = %T; want *dnsRR_SRV", idx, rr) } } - _, addrs, err := answer("_xmpp-server._tcp.google.com.", "foo:53", msg, uint16(dnsTypeSRV)) - if err != nil { - t.Fatalf("answer: %v", err) - } - if g, e := len(addrs), 5; g != e { - t.Errorf("len(addrs) = %d; want %d", g, e) - t.Logf("addrs = %#v", addrs) + for _, name := range [...]string{ + "_xmpp-server._tcp.google.com.", + "_XMPP-Server._TCP.Google.COM.", + "_XMPP-SERVER._TCP.GOOGLE.COM.", + } { + _, addrs, err := answer(name, "foo:53", msg, uint16(dnsTypeSRV)) + if err != nil { + t.Error(err) + } + if g, e := len(addrs), 5; g != e { + t.Errorf("len(addrs) = %d; want %d", g, e) + t.Logf("addrs = %#v", addrs) + } } // repack and unpack. data2, ok := msg.Pack() @@ -46,9 +52,9 @@ func TestDNSParseSRVReply(t *testing.T) { msg2.Unpack(data2) switch { case !ok: - t.Errorf("failed to repack message") + t.Error("failed to repack message") case !reflect.DeepEqual(msg, msg2): - t.Errorf("repacked message differs from original") + t.Error("repacked message differs from original") } } @@ -60,7 +66,7 @@ func TestDNSParseCorruptSRVReply(t *testing.T) { msg := new(dnsMsg) ok := msg.Unpack(data) if !ok { - t.Fatalf("unpacking packet failed") + t.Fatal("unpacking packet failed") } msg.String() // exercise this code path if g, e := len(msg.answer), 5; g != e { @@ -90,6 +96,93 @@ func TestDNSParseCorruptSRVReply(t *testing.T) { } } +func TestDNSParseTXTReply(t *testing.T) { + expectedTxt1 := "v=spf1 redirect=_spf.google.com" + expectedTxt2 := "v=spf1 ip4:69.63.179.25 ip4:69.63.178.128/25 ip4:69.63.184.0/25 " + + "ip4:66.220.144.128/25 ip4:66.220.155.0/24 " + + "ip4:69.171.232.0/25 ip4:66.220.157.0/25 " + + "ip4:69.171.244.0/24 mx -all" + + replies := []string{dnsTXTReply1, dnsTXTReply2} + expectedTxts := []string{expectedTxt1, expectedTxt2} + + for i := range replies { + data, err := hex.DecodeString(replies[i]) + if err != nil { + t.Fatal(err) + } + + msg := new(dnsMsg) + ok := msg.Unpack(data) + if !ok { + t.Errorf("test %d: unpacking packet failed", i) + continue + } + + if len(msg.answer) != 1 { + t.Errorf("test %d: len(rr.answer) = %d; want 1", i, len(msg.answer)) + continue + } + + rr := msg.answer[0] + rrTXT, ok := rr.(*dnsRR_TXT) + if !ok { + t.Errorf("test %d: answer[0] = %T; want *dnsRR_TXT", i, rr) + continue + } + + if rrTXT.Txt != expectedTxts[i] { + t.Errorf("test %d: Txt = %s; want %s", i, rrTXT.Txt, expectedTxts[i]) + } + } +} + +func TestDNSParseTXTCorruptDataLengthReply(t *testing.T) { + replies := []string{dnsTXTCorruptDataLengthReply1, dnsTXTCorruptDataLengthReply2} + + for i := range replies { + data, err := hex.DecodeString(replies[i]) + if err != nil { + t.Fatal(err) + } + + msg := new(dnsMsg) + ok := msg.Unpack(data) + if ok { + t.Errorf("test %d: expected to fail on unpacking corrupt packet", i) + } + } +} + +func TestDNSParseTXTCorruptTXTLengthReply(t *testing.T) { + replies := []string{dnsTXTCorruptTXTLengthReply1, dnsTXTCorruptTXTLengthReply2} + + for i := range replies { + data, err := hex.DecodeString(replies[i]) + if err != nil { + t.Fatal(err) + } + + msg := new(dnsMsg) + ok := msg.Unpack(data) + // Unpacking should succeed, but we should just get the header. + if !ok { + t.Errorf("test %d: unpacking packet failed", i) + continue + } + + if len(msg.answer) != 1 { + t.Errorf("test %d: len(rr.answer) = %d; want 1", i, len(msg.answer)) + continue + } + + rr := msg.answer[0] + if _, justHeader := rr.(*dnsRR_Header); !justHeader { + t.Errorf("test %d: rr = %T; expected *dnsRR_Header", i, rr) + } + } +} + // Valid DNS SRV reply const dnsSRVReply = "0901818000010005000000000c5f786d70702d736572766572045f74637006676f6f67" + "6c6503636f6d0000210001c00c002100010000012c00210014000014950c786d70702d" + @@ -111,3 +204,63 @@ const dnsSRVCorruptReply = "0901818000010005000000000c5f786d70702d73657276657204 "6503636f6d00c00c002100010000012c00200005000014950b786d70702d7365727665" + "72016c06676f6f676c6503636f6d00c00c002100010000012c00FF0014000014950c78" + "6d70702d73657276657231016c06676f6f676c6503636f6d00" + +// TXT reply with one <character-string> +const dnsTXTReply1 = "b3458180000100010004000505676d61696c03636f6d0000100001c00c001000010000012c00" + + "201f763d737066312072656469726563743d5f7370662e676f6f676c652e636f6dc00" + + "c0002000100025d4c000d036e733406676f6f676c65c012c00c0002000100025d4c00" + + "06036e7331c057c00c0002000100025d4c0006036e7333c057c00c0002000100025d4" + + "c0006036e7332c057c06c00010001000248b50004d8ef200ac09000010001000248b5" + + "0004d8ef220ac07e00010001000248b50004d8ef240ac05300010001000248b50004d" + + "8ef260a0000291000000000000000" + +// TXT reply with more than one <character-string>. +// See https://tools.ietf.org/html/rfc1035#section-3.3.14 +const dnsTXTReply2 = "a0a381800001000100020002045f7370660866616365626f6f6b03636f6d0000100001c00c0010000" + + "100000e1000af7f763d73706631206970343a36392e36332e3137392e3235206970343a36392e" + + "36332e3137382e3132382f3235206970343a36392e36332e3138342e302f3235206970343a363" + + "62e3232302e3134342e3132382f3235206970343a36362e3232302e3135352e302f3234206970" + + "343a36392e3137312e3233322e302f323520692e70343a36362e3232302e3135372e302f32352" + + "06970343a36392e3137312e3234342e302f3234206d78202d616c6cc0110002000100025d1500" + + "070161026e73c011c0110002000100025d1500040162c0ecc0ea0001000100025d15000445abe" + + "f0cc0fd0001000100025d15000445abff0c" + +// DataLength field should be sum of all TXT fields. In this case it's less. +const dnsTXTCorruptDataLengthReply1 = "a0a381800001000100020002045f7370660866616365626f6f6b03636f6d0000100001c00c0010000" + + "100000e1000967f763d73706631206970343a36392e36332e3137392e3235206970343a36392e" + + "36332e3137382e3132382f3235206970343a36392e36332e3138342e302f3235206970343a363" + + "62e3232302e3134342e3132382f3235206970343a36362e3232302e3135352e302f3234206970" + + "343a36392e3137312e3233322e302f323520692e70343a36362e3232302e3135372e302f32352" + + "06970343a36392e3137312e3234342e302f3234206d78202d616c6cc0110002000100025d1500" + + "070161026e73c011c0110002000100025d1500040162c0ecc0ea0001000100025d15000445abe" + + "f0cc0fd0001000100025d15000445abff0c" + +// Same as above but DataLength is more than sum of TXT fields. +const dnsTXTCorruptDataLengthReply2 = "a0a381800001000100020002045f7370660866616365626f6f6b03636f6d0000100001c00c0010000" + + "100000e1001227f763d73706631206970343a36392e36332e3137392e3235206970343a36392e" + + "36332e3137382e3132382f3235206970343a36392e36332e3138342e302f3235206970343a363" + + "62e3232302e3134342e3132382f3235206970343a36362e3232302e3135352e302f3234206970" + + "343a36392e3137312e3233322e302f323520692e70343a36362e3232302e3135372e302f32352" + + "06970343a36392e3137312e3234342e302f3234206d78202d616c6cc0110002000100025d1500" + + "070161026e73c011c0110002000100025d1500040162c0ecc0ea0001000100025d15000445abe" + + "f0cc0fd0001000100025d15000445abff0c" + +// TXT Length field is less than actual length. +const dnsTXTCorruptTXTLengthReply1 = "a0a381800001000100020002045f7370660866616365626f6f6b03636f6d0000100001c00c0010000" + + "100000e1000af7f763d73706631206970343a36392e36332e3137392e3235206970343a36392e" + + "36332e3137382e3132382f3235206970343a36392e36332e3138342e302f3235206970343a363" + + "62e3232302e3134342e3132382f3235206970343a36362e3232302e3135352e302f3234206970" + + "343a36392e3137312e3233322e302f323520691470343a36362e3232302e3135372e302f32352" + + "06970343a36392e3137312e3234342e302f3234206d78202d616c6cc0110002000100025d1500" + + "070161026e73c011c0110002000100025d1500040162c0ecc0ea0001000100025d15000445abe" + + "f0cc0fd0001000100025d15000445abff0c" + +// TXT Length field is more than actual length. +const dnsTXTCorruptTXTLengthReply2 = "a0a381800001000100020002045f7370660866616365626f6f6b03636f6d0000100001c00c0010000" + + "100000e1000af7f763d73706631206970343a36392e36332e3137392e3235206970343a36392e" + + "36332e3137382e3132382f3235206970343a36392e36332e3138342e302f3235206970343a363" + + "62e3232302e3134342e3132382f3235206970343a36362e3232302e3135352e302f3234206970" + + "343a36392e3137312e3233322e302f323520693370343a36362e3232302e3135372e302f32352" + + "06970343a36392e3137312e3234342e302f3234206d78202d616c6cc0110002000100025d1500" + + "070161026e73c011c0110002000100025d1500040162c0ecc0ea0001000100025d15000445abe" + + "f0cc0fd0001000100025d15000445abff0c" diff --git a/libgo/go/net/dnsname_test.go b/libgo/go/net/dnsname_test.go index 57dd25fe4c6..be07dc6a16f 100644 --- a/libgo/go/net/dnsname_test.go +++ b/libgo/go/net/dnsname_test.go @@ -9,12 +9,12 @@ import ( "testing" ) -type testCase struct { +type dnsNameTest struct { name string result bool } -var tests = []testCase{ +var dnsNameTests = []dnsNameTest{ // RFC2181, section 11. {"_xmpp-server._tcp.google.com", true}, {"foo.com", true}, @@ -30,7 +30,7 @@ var tests = []testCase{ {"b.com.", true}, } -func getTestCases(ch chan<- testCase) { +func emitDNSNameTest(ch chan<- dnsNameTest) { defer close(ch) var char59 = "" var char63 = "" @@ -41,35 +41,36 @@ func getTestCases(ch chan<- testCase) { char63 = char59 + "aaaa" char64 = char63 + "a" - for _, tc := range tests { + for _, tc := range dnsNameTests { ch <- tc } - ch <- testCase{char63 + ".com", true} - ch <- testCase{char64 + ".com", false} + ch <- dnsNameTest{char63 + ".com", true} + ch <- dnsNameTest{char64 + ".com", false} // 255 char name is fine: - ch <- testCase{char59 + "." + char63 + "." + char63 + "." + + ch <- dnsNameTest{char59 + "." + char63 + "." + char63 + "." + char63 + ".com", true} // 256 char name is bad: - ch <- testCase{char59 + "a." + char63 + "." + char63 + "." + + ch <- dnsNameTest{char59 + "a." + char63 + "." + char63 + "." + char63 + ".com", false} } -func TestDNSNames(t *testing.T) { - ch := make(chan testCase) - go getTestCases(ch) +func TestDNSName(t *testing.T) { + ch := make(chan dnsNameTest) + go emitDNSNameTest(ch) for tc := range ch { if isDomainName(tc.name) != tc.result { - t.Errorf("isDomainName(%v) failed: Should be %v", - tc.name, tc.result) + t.Errorf("isDomainName(%q) = %v; want %v", tc.name, !tc.result, tc.result) } } } -func BenchmarkDNSNames(b *testing.B) { - benchmarks := append(tests, []testCase{ +func BenchmarkDNSName(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + + benchmarks := append(dnsNameTests, []dnsNameTest{ {strings.Repeat("a", 63), true}, {strings.Repeat("a", 64), false}, }...) diff --git a/libgo/go/net/error_plan9_test.go b/libgo/go/net/error_plan9_test.go new file mode 100644 index 00000000000..495ea965343 --- /dev/null +++ b/libgo/go/net/error_plan9_test.go @@ -0,0 +1,17 @@ +// Copyright 2015 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 "syscall" + +var ( + errTimedout = syscall.ETIMEDOUT + errOpNotSupported = syscall.EPLAN9 +) + +func isPlatformError(err error) bool { + _, ok := err.(syscall.ErrorString) + return ok +} diff --git a/libgo/go/net/error_posix_test.go b/libgo/go/net/error_posix_test.go new file mode 100644 index 00000000000..981cc837ba4 --- /dev/null +++ b/libgo/go/net/error_posix_test.go @@ -0,0 +1,44 @@ +// Copyright 2015 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 !plan9 + +package net + +import ( + "os" + "syscall" + "testing" +) + +var ( + errTimedout = syscall.ETIMEDOUT + errOpNotSupported = syscall.EOPNOTSUPP +) + +func isPlatformError(err error) bool { + _, ok := err.(syscall.Errno) + return ok +} + +func TestSpuriousENOTAVAIL(t *testing.T) { + for _, tt := range []struct { + error + ok bool + }{ + {syscall.EADDRNOTAVAIL, true}, + {&os.SyscallError{Syscall: "syscall", Err: syscall.EADDRNOTAVAIL}, true}, + {&OpError{Op: "op", Err: syscall.EADDRNOTAVAIL}, true}, + {&OpError{Op: "op", Err: &os.SyscallError{Syscall: "syscall", Err: syscall.EADDRNOTAVAIL}}, true}, + + {syscall.EINVAL, false}, + {&os.SyscallError{Syscall: "syscall", Err: syscall.EINVAL}, false}, + {&OpError{Op: "op", Err: syscall.EINVAL}, false}, + {&OpError{Op: "op", Err: &os.SyscallError{Syscall: "syscall", Err: syscall.EINVAL}}, false}, + } { + if ok := spuriousENOTAVAIL(tt.error); ok != tt.ok { + t.Errorf("spuriousENOTAVAIL(%v) = %v; want %v", tt.error, ok, tt.ok) + } + } +} diff --git a/libgo/go/net/error_test.go b/libgo/go/net/error_test.go new file mode 100644 index 00000000000..bf95ff6108c --- /dev/null +++ b/libgo/go/net/error_test.go @@ -0,0 +1,673 @@ +// Copyright 2015 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 ( + "fmt" + "io" + "io/ioutil" + "net/internal/socktest" + "os" + "runtime" + "testing" + "time" +) + +func (e *OpError) isValid() error { + if e.Op == "" { + return fmt.Errorf("OpError.Op is empty: %v", e) + } + if e.Net == "" { + return fmt.Errorf("OpError.Net is empty: %v", e) + } + for _, addr := range []Addr{e.Source, e.Addr} { + switch addr := addr.(type) { + case nil: + case *TCPAddr: + if addr == nil { + return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e) + } + case *UDPAddr: + if addr == nil { + return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e) + } + case *IPAddr: + if addr == nil { + return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e) + } + case *IPNet: + if addr == nil { + return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e) + } + case *UnixAddr: + if addr == nil { + return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e) + } + case *pipeAddr: + if addr == nil { + return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e) + } + case fileAddr: + if addr == "" { + return fmt.Errorf("OpError.Source or Addr is empty: %#v, %v", addr, e) + } + default: + return fmt.Errorf("OpError.Source or Addr is unknown type: %T, %v", addr, e) + } + } + if e.Err == nil { + return fmt.Errorf("OpError.Err is empty: %v", e) + } + return nil +} + +// parseDialError parses nestedErr and reports whether it is a valid +// error value from Dial, Listen functions. +// It returns nil when nestedErr is valid. +func parseDialError(nestedErr error) error { + if nestedErr == nil { + return nil + } + + switch err := nestedErr.(type) { + case *OpError: + if err := err.isValid(); err != nil { + return err + } + nestedErr = err.Err + goto second + } + return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr) + +second: + if isPlatformError(nestedErr) { + return nil + } + switch err := nestedErr.(type) { + case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *timeoutError, UnknownNetworkError: + return nil + case *os.SyscallError: + nestedErr = err.Err + goto third + } + switch nestedErr { + case errClosing, errMissingAddress: + return nil + } + return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) + +third: + if isPlatformError(nestedErr) { + return nil + } + return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr) +} + +var dialErrorTests = []struct { + network, address string +}{ + {"foo", ""}, + {"bar", "baz"}, + {"datakit", "mh/astro/r70"}, + {"tcp", ""}, + {"tcp", "127.0.0.1:☺"}, + {"tcp", "no-such-name:80"}, + {"tcp", "mh/astro/r70:http"}, + + {"tcp", "127.0.0.1:0"}, + {"udp", "127.0.0.1:0"}, + {"ip:icmp", "127.0.0.1"}, + + {"unix", "/path/to/somewhere"}, + {"unixgram", "/path/to/somewhere"}, + {"unixpacket", "/path/to/somewhere"}, +} + +func TestDialError(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("%s does not have full support of socktest", runtime.GOOS) + } + + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = func(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { + return nil, &DNSError{Err: "dial error test", Name: "name", Server: "server", IsTimeout: true} + } + sw.Set(socktest.FilterConnect, func(so *socktest.Status) (socktest.AfterFilter, error) { + return nil, errOpNotSupported + }) + defer sw.Set(socktest.FilterConnect, nil) + + d := Dialer{Timeout: someTimeout} + for i, tt := range dialErrorTests { + c, err := d.Dial(tt.network, tt.address) + if err == nil { + t.Errorf("#%d: should fail; %s:%s->%s", i, tt.network, c.LocalAddr(), c.RemoteAddr()) + c.Close() + continue + } + if c != nil { + t.Errorf("Dial returned non-nil interface %T(%v) with err != nil", c, c) + } + if err = parseDialError(err); err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + } +} + +func TestProtocolDialError(t *testing.T) { + switch runtime.GOOS { + case "nacl", "solaris": + t.Skipf("not supported on %s", runtime.GOOS) + } + + for _, network := range []string{"tcp", "udp", "ip:4294967296", "unix", "unixpacket", "unixgram"} { + var err error + switch network { + case "tcp": + _, err = DialTCP(network, nil, &TCPAddr{Port: 1 << 16}) + case "udp": + _, err = DialUDP(network, nil, &UDPAddr{Port: 1 << 16}) + case "ip:4294967296": + _, err = DialIP(network, nil, nil) + case "unix", "unixpacket", "unixgram": + _, err = DialUnix(network, nil, &UnixAddr{Name: "//"}) + } + if err == nil { + t.Errorf("%s: should fail", network) + continue + } + if err = parseDialError(err); err != nil { + t.Errorf("%s: %v", network, err) + continue + } + } +} + +var listenErrorTests = []struct { + network, address string +}{ + {"foo", ""}, + {"bar", "baz"}, + {"datakit", "mh/astro/r70"}, + {"tcp", "127.0.0.1:☺"}, + {"tcp", "no-such-name:80"}, + {"tcp", "mh/astro/r70:http"}, + + {"tcp", "127.0.0.1:0"}, + + {"unix", "/path/to/somewhere"}, + {"unixpacket", "/path/to/somewhere"}, +} + +func TestListenError(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("%s does not have full support of socktest", runtime.GOOS) + } + + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = func(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { + return nil, &DNSError{Err: "listen error test", Name: "name", Server: "server", IsTimeout: true} + } + sw.Set(socktest.FilterListen, func(so *socktest.Status) (socktest.AfterFilter, error) { + return nil, errOpNotSupported + }) + defer sw.Set(socktest.FilterListen, nil) + + for i, tt := range listenErrorTests { + ln, err := Listen(tt.network, tt.address) + if err == nil { + t.Errorf("#%d: should fail; %s:%s->", i, tt.network, ln.Addr()) + ln.Close() + continue + } + if ln != nil { + t.Errorf("Listen returned non-nil interface %T(%v) with err != nil", ln, ln) + } + if err = parseDialError(err); err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + } +} + +var listenPacketErrorTests = []struct { + network, address string +}{ + {"foo", ""}, + {"bar", "baz"}, + {"datakit", "mh/astro/r70"}, + {"udp", "127.0.0.1:☺"}, + {"udp", "no-such-name:80"}, + {"udp", "mh/astro/r70:http"}, +} + +func TestListenPacketError(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("%s does not have full support of socktest", runtime.GOOS) + } + + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = func(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { + return nil, &DNSError{Err: "listen error test", Name: "name", Server: "server", IsTimeout: true} + } + + for i, tt := range listenPacketErrorTests { + c, err := ListenPacket(tt.network, tt.address) + if err == nil { + t.Errorf("#%d: should fail; %s:%s->", i, tt.network, c.LocalAddr()) + c.Close() + continue + } + if c != nil { + t.Errorf("ListenPacket returned non-nil interface %T(%v) with err != nil", c, c) + } + if err = parseDialError(err); err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + } +} + +func TestProtocolListenError(t *testing.T) { + switch runtime.GOOS { + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + + for _, network := range []string{"tcp", "udp", "ip:4294967296", "unix", "unixpacket", "unixgram"} { + var err error + switch network { + case "tcp": + _, err = ListenTCP(network, &TCPAddr{Port: 1 << 16}) + case "udp": + _, err = ListenUDP(network, &UDPAddr{Port: 1 << 16}) + case "ip:4294967296": + _, err = ListenIP(network, nil) + case "unix", "unixpacket": + _, err = ListenUnix(network, &UnixAddr{Name: "//"}) + case "unixgram": + _, err = ListenUnixgram(network, &UnixAddr{Name: "//"}) + } + if err == nil { + t.Errorf("%s: should fail", network) + continue + } + if err = parseDialError(err); err != nil { + t.Errorf("%s: %v", network, err) + continue + } + } +} + +// parseReadError parses nestedErr and reports whether it is a valid +// error value from Read functions. +// It returns nil when nestedErr is valid. +func parseReadError(nestedErr error) error { + if nestedErr == nil { + return nil + } + + switch err := nestedErr.(type) { + case *OpError: + if err := err.isValid(); err != nil { + return err + } + nestedErr = err.Err + goto second + } + if nestedErr == io.EOF { + return nil + } + return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr) + +second: + if isPlatformError(nestedErr) { + return nil + } + switch err := nestedErr.(type) { + case *os.SyscallError: + nestedErr = err.Err + goto third + } + switch nestedErr { + case errClosing, errTimeout: + return nil + } + return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) + +third: + if isPlatformError(nestedErr) { + return nil + } + return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr) +} + +// parseWriteError parses nestedErr and reports whether it is a valid +// error value from Write functions. +// It returns nil when nestedErr is valid. +func parseWriteError(nestedErr error) error { + if nestedErr == nil { + return nil + } + + switch err := nestedErr.(type) { + case *OpError: + if err := err.isValid(); err != nil { + return err + } + nestedErr = err.Err + goto second + } + return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr) + +second: + if isPlatformError(nestedErr) { + return nil + } + switch err := nestedErr.(type) { + case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *timeoutError, UnknownNetworkError: + return nil + case *os.SyscallError: + nestedErr = err.Err + goto third + } + switch nestedErr { + case errClosing, errTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF: + return nil + } + return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) + +third: + if isPlatformError(nestedErr) { + return nil + } + return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr) +} + +// parseCloseError parses nestedErr and reports whether it is a valid +// error value from Close functions. +// It returns nil when nestedErr is valid. +func parseCloseError(nestedErr error) error { + if nestedErr == nil { + return nil + } + + switch err := nestedErr.(type) { + case *OpError: + if err := err.isValid(); err != nil { + return err + } + nestedErr = err.Err + goto second + } + return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr) + +second: + if isPlatformError(nestedErr) { + return nil + } + switch err := nestedErr.(type) { + case *os.SyscallError: + nestedErr = err.Err + goto third + case *os.PathError: // for Plan 9 + nestedErr = err.Err + goto third + } + switch nestedErr { + case errClosing: + return nil + } + return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) + +third: + if isPlatformError(nestedErr) { + return nil + } + return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr) +} + +func TestCloseError(t *testing.T) { + ln, err := newLocalListener("tcp") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + for i := 0; i < 3; i++ { + err = c.(*TCPConn).CloseRead() + if perr := parseCloseError(err); perr != nil { + t.Errorf("#%d: %v", i, perr) + } + } + for i := 0; i < 3; i++ { + err = c.(*TCPConn).CloseWrite() + if perr := parseCloseError(err); perr != nil { + t.Errorf("#%d: %v", i, perr) + } + } + for i := 0; i < 3; i++ { + err = c.Close() + if perr := parseCloseError(err); perr != nil { + t.Errorf("#%d: %v", i, perr) + } + err = ln.Close() + if perr := parseCloseError(err); perr != nil { + t.Errorf("#%d: %v", i, perr) + } + } + + pc, err := ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer pc.Close() + + for i := 0; i < 3; i++ { + err = pc.Close() + if perr := parseCloseError(err); perr != nil { + t.Errorf("#%d: %v", i, perr) + } + } +} + +// parseAcceptError parses nestedErr and reports whether it is a valid +// error value from Accept functions. +// It returns nil when nestedErr is valid. +func parseAcceptError(nestedErr error) error { + if nestedErr == nil { + return nil + } + + switch err := nestedErr.(type) { + case *OpError: + if err := err.isValid(); err != nil { + return err + } + nestedErr = err.Err + goto second + } + return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr) + +second: + if isPlatformError(nestedErr) { + return nil + } + switch err := nestedErr.(type) { + case *os.SyscallError: + nestedErr = err.Err + goto third + } + switch nestedErr { + case errClosing, errTimeout: + return nil + } + return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) + +third: + if isPlatformError(nestedErr) { + return nil + } + return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr) +} + +func TestAcceptError(t *testing.T) { + handler := func(ls *localServer, ln Listener) { + for { + ln.(*TCPListener).SetDeadline(time.Now().Add(5 * time.Millisecond)) + c, err := ln.Accept() + if perr := parseAcceptError(err); perr != nil { + t.Error(perr) + } + if err != nil { + if c != nil { + t.Errorf("Accept returned non-nil interface %T(%v) with err != nil", c, c) + } + if nerr, ok := err.(Error); !ok || (!nerr.Timeout() && !nerr.Temporary()) { + return + } + continue + } + c.Close() + } + } + ls, err := newLocalServer("tcp") + if err != nil { + t.Fatal(err) + } + if err := ls.buildup(handler); err != nil { + ls.teardown() + t.Fatal(err) + } + + time.Sleep(100 * time.Millisecond) + ls.teardown() +} + +// parseCommonError parses nestedErr and reports whether it is a valid +// error value from miscellaneous functions. +// It returns nil when nestedErr is valid. +func parseCommonError(nestedErr error) error { + if nestedErr == nil { + return nil + } + + switch err := nestedErr.(type) { + case *OpError: + if err := err.isValid(); err != nil { + return err + } + nestedErr = err.Err + goto second + } + return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr) + +second: + if isPlatformError(nestedErr) { + return nil + } + switch err := nestedErr.(type) { + case *os.SyscallError: + nestedErr = err.Err + goto third + case *os.LinkError: + nestedErr = err.Err + goto third + case *os.PathError: + nestedErr = err.Err + goto third + } + switch nestedErr { + case errClosing: + return nil + } + return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) + +third: + if isPlatformError(nestedErr) { + return nil + } + return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr) +} + +func TestFileError(t *testing.T) { + switch runtime.GOOS { + case "windows": + t.Skipf("not supported on %s", runtime.GOOS) + } + + f, err := ioutil.TempFile("", "go-nettest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + defer f.Close() + + c, err := FileConn(f) + if err != nil { + if c != nil { + t.Errorf("FileConn returned non-nil interface %T(%v) with err != nil", c, c) + } + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + } else { + c.Close() + t.Error("should fail") + } + ln, err := FileListener(f) + if err != nil { + if ln != nil { + t.Errorf("FileListener returned non-nil interface %T(%v) with err != nil", ln, ln) + } + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + } else { + ln.Close() + t.Error("should fail") + } + pc, err := FilePacketConn(f) + if err != nil { + if pc != nil { + t.Errorf("FilePacketConn returned non-nil interface %T(%v) with err != nil", pc, pc) + } + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + } else { + pc.Close() + t.Error("should fail") + } + + ln, err = newLocalListener("tcp") + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 3; i++ { + f, err := ln.(*TCPListener).File() + if err != nil { + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + } else { + f.Close() + } + ln.Close() + } +} diff --git a/libgo/go/net/external_test.go b/libgo/go/net/external_test.go new file mode 100644 index 00000000000..d5ff2be20a3 --- /dev/null +++ b/libgo/go/net/external_test.go @@ -0,0 +1,167 @@ +// 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 ( + "fmt" + "io" + "strings" + "testing" +) + +func TestResolveGoogle(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("avoid external network") + } + if !supportsIPv4 || !supportsIPv6 || !*testIPv4 || !*testIPv6 { + t.Skip("both IPv4 and IPv6 are required") + } + + for _, network := range []string{"tcp", "tcp4", "tcp6"} { + addr, err := ResolveTCPAddr(network, "www.google.com:http") + if err != nil { + t.Error(err) + continue + } + switch { + case network == "tcp" && addr.IP.To4() == nil: + fallthrough + case network == "tcp4" && addr.IP.To4() == nil: + t.Errorf("got %v; want an IPv4 address on %s", addr, network) + case network == "tcp6" && (addr.IP.To16() == nil || addr.IP.To4() != nil): + t.Errorf("got %v; want an IPv6 address on %s", addr, network) + } + } +} + +var dialGoogleTests = []struct { + dial func(string, string) (Conn, error) + unreachableNetwork string + networks []string + addrs []string +}{ + { + dial: (&Dialer{DualStack: true}).Dial, + networks: []string{"tcp", "tcp4", "tcp6"}, + addrs: []string{"www.google.com:http"}, + }, + { + dial: Dial, + unreachableNetwork: "tcp6", + networks: []string{"tcp", "tcp4"}, + }, + { + dial: Dial, + unreachableNetwork: "tcp4", + networks: []string{"tcp", "tcp6"}, + }, +} + +func TestDialGoogle(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("avoid external network") + } + if !supportsIPv4 || !supportsIPv6 || !*testIPv4 || !*testIPv6 { + t.Skip("both IPv4 and IPv6 are required") + } + + var err error + dialGoogleTests[1].addrs, dialGoogleTests[2].addrs, err = googleLiteralAddrs() + if err != nil { + t.Error(err) + } + for _, tt := range dialGoogleTests { + for _, network := range tt.networks { + disableSocketConnect(tt.unreachableNetwork) + for _, addr := range tt.addrs { + if err := fetchGoogle(tt.dial, network, addr); err != nil { + t.Error(err) + } + } + enableSocketConnect() + } + } +} + +var ( + literalAddrs4 = [...]string{ + "%d.%d.%d.%d:80", + "www.google.com:80", + "%d.%d.%d.%d:http", + "www.google.com:http", + "%03d.%03d.%03d.%03d:0080", + "[::ffff:%d.%d.%d.%d]:80", + "[::ffff:%02x%02x:%02x%02x]:80", + "[0:0:0:0:0000:ffff:%d.%d.%d.%d]:80", + "[0:0:0:0:000000:ffff:%d.%d.%d.%d]:80", + "[0:0:0:0::ffff:%d.%d.%d.%d]:80", + } + literalAddrs6 = [...]string{ + "[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:80", + "ipv6.google.com:80", + "[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:http", + "ipv6.google.com:http", + } +) + +func googleLiteralAddrs() (lits4, lits6 []string, err error) { + ips, err := LookupIP("www.google.com") + if err != nil { + return nil, nil, err + } + if len(ips) == 0 { + return nil, nil, nil + } + var ip4, ip6 IP + for _, ip := range ips { + if ip4 == nil && ip.To4() != nil { + ip4 = ip.To4() + } + if ip6 == nil && ip.To16() != nil && ip.To4() == nil { + ip6 = ip.To16() + } + if ip4 != nil && ip6 != nil { + break + } + } + if ip4 != nil { + for i, lit4 := range literalAddrs4 { + if strings.Contains(lit4, "%") { + literalAddrs4[i] = fmt.Sprintf(lit4, ip4[0], ip4[1], ip4[2], ip4[3]) + } + } + lits4 = literalAddrs4[:] + } + if ip6 != nil { + for i, lit6 := range literalAddrs6 { + if strings.Contains(lit6, "%") { + literalAddrs6[i] = fmt.Sprintf(lit6, ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7], ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15]) + } + } + lits6 = literalAddrs6[:] + } + return +} + +func fetchGoogle(dial func(string, string) (Conn, error), network, address string) error { + c, err := dial(network, address) + if err != nil { + return err + } + defer c.Close() + req := []byte("GET /robots.txt HTTP/1.0\r\nHost: www.google.com\r\n\r\n") + if _, err := c.Write(req); err != nil { + return err + } + b := make([]byte, 1000) + n, err := io.ReadFull(c, b) + if err != nil { + return err + } + if n < 1000 { + return fmt.Errorf("short read from %s:%s->%s", network, c.RemoteAddr(), c.LocalAddr()) + } + return nil +} diff --git a/libgo/go/net/fd_plan9.go b/libgo/go/net/fd_plan9.go index 5fe8effc295..32766f53b58 100644 --- a/libgo/go/net/fd_plan9.go +++ b/libgo/go/net/fd_plan9.go @@ -11,13 +11,13 @@ import ( "time" ) -// Network file descritor. +// Network file descriptor. type netFD struct { // locking/lifetime of sysfd + serialize access to Read and Write methods fdmu fdMutex // immutable until Close - proto string + net string n string dir string ctl, data *os.File @@ -38,8 +38,8 @@ func dial(net string, ra Addr, dialer func(time.Time) (Conn, error), deadline ti return dialChannel(net, ra, dialer, deadline) } -func newFD(proto, name string, ctl, data *os.File, laddr, raddr Addr) (*netFD, error) { - return &netFD{proto: proto, n: name, dir: netdir + "/" + proto + "/" + name, ctl: ctl, data: data, laddr: laddr, raddr: raddr}, nil +func newFD(net, name string, ctl, data *os.File, laddr, raddr Addr) (*netFD, error) { + return &netFD{net: net, n: name, dir: netdir + "/" + net + "/" + name, ctl: ctl, data: data, laddr: laddr, raddr: raddr}, nil } func (fd *netFD) init() error { @@ -55,7 +55,7 @@ func (fd *netFD) name() string { if fd.raddr != nil { rs = fd.raddr.String() } - return fd.proto + ":" + ls + "->" + rs + return fd.net + ":" + ls + "->" + rs } func (fd *netFD) ok() bool { return fd != nil && fd.ctl != nil } @@ -132,7 +132,7 @@ func (fd *netFD) Read(b []byte) (n int, err error) { } defer fd.readUnlock() n, err = fd.data.Read(b) - if fd.proto == "udp" && err == io.EOF { + if fd.net == "udp" && err == io.EOF { n = 0 err = nil } @@ -202,7 +202,7 @@ func (fd *netFD) file(f *os.File, s string) (*os.File, error) { dfd, err := syscall.Dup(int(f.Fd()), -1) syscall.ForkLock.RUnlock() if err != nil { - return nil, &OpError{"dup", s, fd.laddr, err} + return nil, os.NewSyscallError("dup", err) } return os.NewFile(uintptr(dfd), s), nil } @@ -226,7 +226,3 @@ func setReadBuffer(fd *netFD, bytes int) error { func setWriteBuffer(fd *netFD, bytes int) error { return syscall.EPLAN9 } - -func skipRawSocketTests() (skip bool, skipmsg string, err error) { - return true, "skipping test on plan9", nil -} diff --git a/libgo/go/net/fd_poll_nacl.go b/libgo/go/net/fd_poll_nacl.go index a3701f87648..cdf14e32ce8 100644 --- a/libgo/go/net/fd_poll_nacl.go +++ b/libgo/go/net/fd_poll_nacl.go @@ -18,18 +18,11 @@ func (pd *pollDesc) Init(fd *netFD) error { pd.fd = fd; return nil } func (pd *pollDesc) Close() {} -func (pd *pollDesc) Lock() {} - -func (pd *pollDesc) Unlock() {} - -func (pd *pollDesc) Wakeup() {} - -func (pd *pollDesc) Evict() bool { +func (pd *pollDesc) Evict() { pd.closing = true if pd.fd != nil { syscall.StopIO(pd.fd.sysfd) } - return false } func (pd *pollDesc) Prepare(mode int) error { diff --git a/libgo/go/net/fd_poll_runtime.go b/libgo/go/net/fd_poll_runtime.go index 2bddc836c75..8522ccebfb3 100644 --- a/libgo/go/net/fd_poll_runtime.go +++ b/libgo/go/net/fd_poll_runtime.go @@ -48,23 +48,12 @@ func (pd *pollDesc) Close() { pd.runtimeCtx = 0 } -func (pd *pollDesc) Lock() { -} - -func (pd *pollDesc) Unlock() { -} - -func (pd *pollDesc) Wakeup() { -} - // Evict evicts fd from the pending list, unblocking any I/O running on fd. -// Return value is whether the pollServer should be woken up. -func (pd *pollDesc) Evict() bool { +func (pd *pollDesc) Evict() { if pd.runtimeCtx == 0 { - return false + return } runtime_pollUnblock(pd.runtimeCtx) - return false } func (pd *pollDesc) Prepare(mode int) error { diff --git a/libgo/go/net/fd_posix.go b/libgo/go/net/fd_posix.go new file mode 100644 index 00000000000..b4b908abacf --- /dev/null +++ b/libgo/go/net/fd_posix.go @@ -0,0 +1,21 @@ +// 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. + +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows + +package net + +import ( + "io" + "syscall" +) + +// eofError returns io.EOF when fd is available for reading end of +// file. +func (fd *netFD) eofError(n int, err error) error { + if n == 0 && err == nil && fd.sotype != syscall.SOCK_DGRAM && fd.sotype != syscall.SOCK_RAW { + return io.EOF + } + return err +} diff --git a/libgo/go/net/fd_unix_test.go b/libgo/go/net/fd_posix_test.go index fe8e8ff6a88..85711ef1b70 100644 --- a/libgo/go/net/fd_unix_test.go +++ b/libgo/go/net/fd_posix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd solaris +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net @@ -12,13 +12,12 @@ import ( "testing" ) -var chkReadErrTests = []struct { +var eofErrorTests = []struct { n int err error fd *netFD expected error }{ - {100, nil, &netFD{sotype: syscall.SOCK_STREAM}, nil}, {100, io.EOF, &netFD{sotype: syscall.SOCK_STREAM}, io.EOF}, {100, errClosing, &netFD{sotype: syscall.SOCK_STREAM}, errClosing}, @@ -48,11 +47,11 @@ var chkReadErrTests = []struct { {0, errClosing, &netFD{sotype: syscall.SOCK_RAW}, errClosing}, } -func TestChkReadErr(t *testing.T) { - for _, tt := range chkReadErrTests { - actual := chkReadErr(tt.n, tt.err, tt.fd) +func TestEOFError(t *testing.T) { + for _, tt := range eofErrorTests { + actual := tt.fd.eofError(tt.n, tt.err) if actual != tt.expected { - t.Errorf("chkReadError(%v, %v, %v): expected %v, actual %v", tt.n, tt.err, tt.fd.sotype, tt.expected, actual) + t.Errorf("eofError(%v, %v, %v): expected %v, actual %v", tt.n, tt.err, tt.fd.sotype, tt.expected, actual) } } } diff --git a/libgo/go/net/fd_unix.go b/libgo/go/net/fd_unix.go index 45a4eb8f123..465023fb23d 100644 --- a/libgo/go/net/fd_unix.go +++ b/libgo/go/net/fd_unix.go @@ -72,7 +72,7 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error { // Do not need to call fd.writeLock here, // because fd is not yet accessible to user, // so no concurrent operations are possible. - switch err := syscall.Connect(fd.sysfd, ra); err { + switch err := connectFunc(fd.sysfd, ra); err { case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: case nil, syscall.EISCONN: if !deadline.IsZero() && deadline.Before(time.Now()) { @@ -87,13 +87,13 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error { // already been accepted and closed by the server. // Treat this as a successful connection--writes to // the socket will see EOF. For details and a test - // case in C see http://golang.org/issue/6828. + // case in C see https://golang.org/issue/6828. if runtime.GOOS == "solaris" { return nil } fallthrough default: - return err + return os.NewSyscallError("connect", err) } if err := fd.init(); err != nil { return err @@ -114,25 +114,25 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error { if err := fd.pd.WaitWrite(); err != nil { return err } - nerr, err := syscall.GetsockoptInt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR) + nerr, err := getsockoptIntFunc(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR) if err != nil { - return err + return os.NewSyscallError("getsockopt", err) } switch err := syscall.Errno(nerr); err { case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: case syscall.Errno(0), syscall.EISCONN: return nil default: - return err + return os.NewSyscallError("getsockopt", err) } } } func (fd *netFD) destroy() { // Poller may want to unregister fd in readiness notification mechanism, - // so this must be executed before closesocket. + // so this must be executed before closeFunc. fd.pd.Close() - closesocket(fd.sysfd) + closeFunc(fd.sysfd) fd.sysfd = -1 runtime.SetFinalizer(fd, nil) } @@ -187,9 +187,7 @@ func (fd *netFD) writeUnlock() { } func (fd *netFD) Close() error { - fd.pd.Lock() // needed for both fd.incref(true) and pollDesc.Evict if !fd.fdmu.IncrefAndClose() { - fd.pd.Unlock() return errClosing } // Unblock any I/O. Once it all unblocks and returns, @@ -197,12 +195,8 @@ func (fd *netFD) Close() error { // the final decref will close fd.sysfd. This should happen // fairly quickly, since all the I/O is non-blocking, and any // attempts to block in the pollDesc will return errClosing. - doWakeup := fd.pd.Evict() - fd.pd.Unlock() + fd.pd.Evict() fd.decref() - if doWakeup { - fd.pd.Wakeup() - } return nil } @@ -211,11 +205,7 @@ func (fd *netFD) shutdown(how int) error { return err } defer fd.decref() - err := syscall.Shutdown(fd.sysfd, how) - if err != nil { - return &OpError{"shutdown", fd.net, fd.laddr, err} - } - return nil + return os.NewSyscallError("shutdown", syscall.Shutdown(fd.sysfd, how)) } func (fd *netFD) closeRead() error { @@ -232,10 +222,10 @@ func (fd *netFD) Read(p []byte) (n int, err error) { } defer fd.readUnlock() if err := fd.pd.PrepareRead(); err != nil { - return 0, &OpError{"read", fd.net, fd.raddr, err} + return 0, err } for { - n, err = syscall.Read(int(fd.sysfd), p) + n, err = syscall.Read(fd.sysfd, p) if err != nil { n = 0 if err == syscall.EAGAIN { @@ -244,11 +234,11 @@ func (fd *netFD) Read(p []byte) (n int, err error) { } } } - err = chkReadErr(n, err, fd) + err = fd.eofError(n, err) break } - if err != nil && err != io.EOF { - err = &OpError{"read", fd.net, fd.raddr, err} + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("read", err) } return } @@ -259,7 +249,7 @@ func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { } defer fd.readUnlock() if err := fd.pd.PrepareRead(); err != nil { - return 0, nil, &OpError{"read", fd.net, fd.laddr, err} + return 0, nil, err } for { n, sa, err = syscall.Recvfrom(fd.sysfd, p, 0) @@ -271,11 +261,11 @@ func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { } } } - err = chkReadErr(n, err, fd) + err = fd.eofError(n, err) break } - if err != nil && err != io.EOF { - err = &OpError{"read", fd.net, fd.laddr, err} + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("recvfrom", err) } return } @@ -286,7 +276,7 @@ func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.S } defer fd.readUnlock() if err := fd.pd.PrepareRead(); err != nil { - return 0, 0, 0, nil, &OpError{"read", fd.net, fd.laddr, err} + return 0, 0, 0, nil, err } for { n, oobn, flags, sa, err = syscall.Recvmsg(fd.sysfd, p, oob, 0) @@ -298,33 +288,26 @@ func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.S } } } - err = chkReadErr(n, err, fd) + err = fd.eofError(n, err) break } - if err != nil && err != io.EOF { - err = &OpError{"read", fd.net, fd.laddr, err} + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("recvmsg", err) } return } -func chkReadErr(n int, err error, fd *netFD) error { - if n == 0 && err == nil && fd.sotype != syscall.SOCK_DGRAM && fd.sotype != syscall.SOCK_RAW { - return io.EOF - } - return err -} - func (fd *netFD) Write(p []byte) (nn int, err error) { if err := fd.writeLock(); err != nil { return 0, err } defer fd.writeUnlock() if err := fd.pd.PrepareWrite(); err != nil { - return 0, &OpError{"write", fd.net, fd.raddr, err} + return 0, err } for { var n int - n, err = syscall.Write(int(fd.sysfd), p[nn:]) + n, err = syscall.Write(fd.sysfd, p[nn:]) if n > 0 { nn += n } @@ -337,7 +320,6 @@ func (fd *netFD) Write(p []byte) (nn int, err error) { } } if err != nil { - n = 0 break } if n == 0 { @@ -345,8 +327,8 @@ func (fd *netFD) Write(p []byte) (nn int, err error) { break } } - if err != nil { - err = &OpError{"write", fd.net, fd.raddr, err} + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("write", err) } return nn, err } @@ -357,7 +339,7 @@ func (fd *netFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) { } defer fd.writeUnlock() if err := fd.pd.PrepareWrite(); err != nil { - return 0, &OpError{"write", fd.net, fd.raddr, err} + return 0, err } for { err = syscall.Sendto(fd.sysfd, p, 0, sa) @@ -370,8 +352,9 @@ func (fd *netFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) { } if err == nil { n = len(p) - } else { - err = &OpError{"write", fd.net, fd.raddr, err} + } + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("sendto", err) } return } @@ -382,7 +365,7 @@ func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oob } defer fd.writeUnlock() if err := fd.pd.PrepareWrite(); err != nil { - return 0, 0, &OpError{"write", fd.net, fd.raddr, err} + return 0, 0, err } for { n, err = syscall.SendmsgN(fd.sysfd, p, oob, sa, 0) @@ -395,8 +378,9 @@ func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oob } if err == nil { oobn = len(oob) - } else { - err = &OpError{"write", fd.net, fd.raddr, err} + } + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("sendmsg", err) } return } @@ -410,27 +394,34 @@ func (fd *netFD) accept() (netfd *netFD, err error) { var s int var rsa syscall.Sockaddr if err = fd.pd.PrepareRead(); err != nil { - return nil, &OpError{"accept", fd.net, fd.laddr, err} + return nil, err } for { s, rsa, err = accept(fd.sysfd) if err != nil { - if err == syscall.EAGAIN { + nerr, ok := err.(*os.SyscallError) + if !ok { + return nil, err + } + switch nerr.Err { + case syscall.EAGAIN: if err = fd.pd.WaitRead(); err == nil { continue } - } else if err == syscall.ECONNABORTED { - // This means that a socket on the listen queue was closed - // before we Accept()ed it; it's a silly error, so try again. + case syscall.ECONNABORTED: + // This means that a socket on the + // listen queue was closed before we + // Accept()ed it; it's a silly error, + // so try again. continue } - return nil, &OpError{"accept", fd.net, fd.laddr, err} + return nil, err } break } if netfd, err = newFD(s, fd.family, fd.sotype, fd.net); err != nil { - closesocket(s) + closeFunc(s) return nil, err } if err = netfd.init(); err != nil { @@ -478,7 +469,7 @@ func dupCloseOnExec(fd int) (newfd int, err error) { // from now on. atomic.StoreInt32(&tryDupCloexec, 0) default: - return -1, e1 + return -1, os.NewSyscallError("fcntl", e1) } } return dupCloseOnExecOld(fd) @@ -491,7 +482,7 @@ func dupCloseOnExecOld(fd int) (newfd int, err error) { defer syscall.ForkLock.RUnlock() newfd, err = syscall.Dup(fd) if err != nil { - return -1, err + return -1, os.NewSyscallError("dup", err) } syscall.CloseOnExec(newfd) return @@ -500,7 +491,7 @@ func dupCloseOnExecOld(fd int) (newfd int, err error) { func (fd *netFD) dup() (f *os.File, err error) { ns, err := dupCloseOnExec(fd.sysfd) if err != nil { - return nil, &OpError{"dup", fd.net, fd.laddr, err} + return nil, err } // We want blocking mode for the new fd, hence the double negative. @@ -508,19 +499,8 @@ func (fd *netFD) dup() (f *os.File, err error) { // I/O will block the thread instead of letting us use the epoll server. // Everything will still work, just with more threads. if err = syscall.SetNonblock(ns, false); err != nil { - return nil, &OpError{"setnonblock", fd.net, fd.laddr, err} + return nil, os.NewSyscallError("setnonblock", err) } return os.NewFile(uintptr(ns), fd.name()), nil } - -func closesocket(s int) error { - return syscall.Close(s) -} - -func skipRawSocketTests() (skip bool, skipmsg string, err error) { - if os.Getuid() != 0 { - return true, "skipping test; must be root", nil - } - return false, "", nil -} diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go index f3a534a1de0..205daff9e46 100644 --- a/libgo/go/net/fd_windows.go +++ b/libgo/go/net/fd_windows.go @@ -5,8 +5,6 @@ package net import ( - "errors" - "io" "os" "runtime" "sync" @@ -40,7 +38,7 @@ func sysInit() { var d syscall.WSAData e := syscall.WSAStartup(uint32(0x202), &d) if e != nil { - initErr = os.NewSyscallError("WSAStartup", e) + initErr = os.NewSyscallError("wsastartup", e) } canCancelIO = syscall.LoadCancelIoEx() == nil if syscall.LoadGetAddrInfo() == nil { @@ -70,10 +68,6 @@ func sysInit() { } } -func closesocket(s syscall.Handle) error { - return syscall.Closesocket(s) -} - func canUseConnectEx(net string) bool { switch net { case "udp", "udp4", "udp6", "ip", "ip4", "ip6": @@ -159,7 +153,7 @@ func (s *ioSrv) ExecIO(o *operation, name string, submit func(o *operation) erro // Notify runtime netpoll about starting IO. err := fd.pd.Prepare(int(o.mode)) if err != nil { - return 0, &OpError{name, fd.net, fd.laddr, err} + return 0, err } // Start IO. if canCancelIO { @@ -182,7 +176,7 @@ func (s *ioSrv) ExecIO(o *operation, name string, submit func(o *operation) erro // IO started, and we have to wait for its completion. err = nil default: - return 0, &OpError{name, fd.net, fd.laddr, err} + return 0, err } // Wait for our request to complete. err = fd.pd.Wait(int(o.mode)) @@ -190,7 +184,7 @@ func (s *ioSrv) ExecIO(o *operation, name string, submit func(o *operation) erro // All is good. Extract our IO results and return. if o.errno != 0 { err = syscall.Errno(o.errno) - return 0, &OpError{name, fd.net, fd.laddr, err} + return 0, err } return int(o.qty), nil } @@ -221,7 +215,7 @@ func (s *ioSrv) ExecIO(o *operation, name string, submit func(o *operation) erro if err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled err = netpollErr } - return 0, &OpError{name, fd.net, fd.laddr, err} + return 0, err } // We issued cancellation request. But, it seems, IO operation succeeded // before cancellation request run. We need to treat IO operation as @@ -303,7 +297,7 @@ func (fd *netFD) init() error { size := uint32(unsafe.Sizeof(flag)) err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0) if err != nil { - return os.NewSyscallError("WSAIoctl", err) + return os.NewSyscallError("wsaioctl", err) } } fd.rop.mode = 'r' @@ -337,7 +331,7 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error { defer fd.setWriteDeadline(noDeadline) } if !canUseConnectEx(fd.net) { - return syscall.Connect(fd.sysfd, ra) + return os.NewSyscallError("connect", connectFunc(fd.sysfd, ra)) } // ConnectEx windows API requires an unconnected, previously bound socket. if la == nil { @@ -350,20 +344,23 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error { panic("unexpected type in connect") } if err := syscall.Bind(fd.sysfd, la); err != nil { - return err + return os.NewSyscallError("bind", err) } } // Call ConnectEx API. o := &fd.wop o.sa = ra _, err := wsrv.ExecIO(o, "ConnectEx", func(o *operation) error { - return syscall.ConnectEx(o.fd.sysfd, o.sa, nil, 0, nil, &o.o) + return connectExFunc(o.fd.sysfd, o.sa, nil, 0, nil, &o.o) }) if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("connectex", err) + } return err } // Refresh socket properties. - return syscall.Setsockopt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd))) + return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd)))) } func (fd *netFD) destroy() { @@ -371,9 +368,9 @@ func (fd *netFD) destroy() { return } // Poller may want to unregister fd in readiness notification mechanism, - // so this must be executed before closesocket. + // so this must be executed before closeFunc. fd.pd.Close() - closesocket(fd.sysfd) + closeFunc(fd.sysfd) fd.sysfd = syscall.InvalidHandle // no need for a finalizer anymore runtime.SetFinalizer(fd, nil) @@ -443,11 +440,7 @@ func (fd *netFD) shutdown(how int) error { return err } defer fd.decref() - err := syscall.Shutdown(fd.sysfd, how) - if err != nil { - return &OpError{"shutdown", fd.net, fd.laddr, err} - } - return nil + return syscall.Shutdown(fd.sysfd, how) } func (fd *netFD) closeRead() error { @@ -468,16 +461,17 @@ func (fd *netFD) Read(buf []byte) (int, error) { n, err := rsrv.ExecIO(o, "WSARecv", func(o *operation) error { return syscall.WSARecv(o.fd.sysfd, &o.buf, 1, &o.qty, &o.flags, &o.o, nil) }) - if err == nil && n == 0 { - err = io.EOF - } if raceenabled { raceAcquire(unsafe.Pointer(&ioSync)) } + err = fd.eofError(n, err) + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsarecv", err) + } return n, err } -func (fd *netFD) readFrom(buf []byte) (n int, sa syscall.Sockaddr, err error) { +func (fd *netFD) readFrom(buf []byte) (int, syscall.Sockaddr, error) { if len(buf) == 0 { return 0, nil, nil } @@ -487,18 +481,22 @@ func (fd *netFD) readFrom(buf []byte) (n int, sa syscall.Sockaddr, err error) { defer fd.readUnlock() o := &fd.rop o.InitBuf(buf) - n, err = rsrv.ExecIO(o, "WSARecvFrom", func(o *operation) error { + n, err := rsrv.ExecIO(o, "WSARecvFrom", func(o *operation) error { if o.rsa == nil { o.rsa = new(syscall.RawSockaddrAny) } o.rsan = int32(unsafe.Sizeof(*o.rsa)) return syscall.WSARecvFrom(o.fd.sysfd, &o.buf, 1, &o.qty, &o.flags, o.rsa, &o.rsan, &o.o, nil) }) + err = fd.eofError(n, err) + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsarecvfrom", err) + } if err != nil { - return 0, nil, err + return n, nil, err } - sa, _ = o.rsa.Sockaddr() - return + sa, _ := o.rsa.Sockaddr() + return n, sa, nil } func (fd *netFD) Write(buf []byte) (int, error) { @@ -511,9 +509,13 @@ func (fd *netFD) Write(buf []byte) (int, error) { } o := &fd.wop o.InitBuf(buf) - return wsrv.ExecIO(o, "WSASend", func(o *operation) error { + n, err := wsrv.ExecIO(o, "WSASend", func(o *operation) error { return syscall.WSASend(o.fd.sysfd, &o.buf, 1, &o.qty, 0, &o.o, nil) }) + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsasend", err) + } + return n, err } func (fd *netFD) writeTo(buf []byte, sa syscall.Sockaddr) (int, error) { @@ -527,23 +529,27 @@ func (fd *netFD) writeTo(buf []byte, sa syscall.Sockaddr) (int, error) { o := &fd.wop o.InitBuf(buf) o.sa = sa - return wsrv.ExecIO(o, "WSASendto", func(o *operation) error { + n, err := wsrv.ExecIO(o, "WSASendto", func(o *operation) error { return syscall.WSASendto(o.fd.sysfd, &o.buf, 1, &o.qty, 0, o.sa, &o.o, nil) }) + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsasendto", err) + } + return n, err } func (fd *netFD) acceptOne(rawsa []syscall.RawSockaddrAny, o *operation) (*netFD, error) { // Get new socket. s, err := sysSocket(fd.family, fd.sotype, 0) if err != nil { - return nil, &OpError{"socket", fd.net, fd.laddr, err} + return nil, err } // Associate our new socket with IOCP. netfd, err := newFD(s, fd.family, fd.sotype, fd.net) if err != nil { - closesocket(s) - return nil, &OpError{"accept", fd.net, fd.laddr, err} + closeFunc(s) + return nil, err } if err := netfd.init(); err != nil { fd.Close() @@ -558,6 +564,9 @@ func (fd *netFD) acceptOne(rawsa []syscall.RawSockaddrAny, o *operation) (*netFD }) if err != nil { netfd.Close() + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("acceptex", err) + } return nil, err } @@ -565,7 +574,7 @@ func (fd *netFD) acceptOne(rawsa []syscall.RawSockaddrAny, o *operation) (*netFD err = syscall.Setsockopt(s, syscall.SOL_SOCKET, syscall.SO_UPDATE_ACCEPT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd))) if err != nil { netfd.Close() - return nil, &OpError{"Setsockopt", fd.net, fd.laddr, err} + return nil, os.NewSyscallError("setsockopt", err) } return netfd, nil @@ -591,11 +600,11 @@ func (fd *netFD) accept() (*netFD, error) { // before AcceptEx could complete. These errors relate to new // connection, not to AcceptEx, so ignore broken connection and // try AcceptEx again for more connections. - operr, ok := err.(*OpError) + nerr, ok := err.(*os.SyscallError) if !ok { return nil, err } - errno, ok := operr.Err.(syscall.Errno) + errno, ok := nerr.Err.(syscall.Errno) if !ok { return nil, err } @@ -619,38 +628,17 @@ func (fd *netFD) accept() (*netFD, error) { return netfd, nil } -func skipRawSocketTests() (skip bool, skipmsg string, err error) { - // From http://msdn.microsoft.com/en-us/library/windows/desktop/ms740548.aspx: - // Note: To use a socket of type SOCK_RAW requires administrative privileges. - // Users running Winsock applications that use raw sockets must be a member of - // the Administrators group on the local computer, otherwise raw socket calls - // will fail with an error code of WSAEACCES. On Windows Vista and later, access - // for raw sockets is enforced at socket creation. In earlier versions of Windows, - // access for raw sockets is enforced during other socket operations. - s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, 0) - if err == syscall.WSAEACCES { - return true, "skipping test; no access to raw socket allowed", nil - } - if err != nil { - return true, "", err - } - defer syscall.Closesocket(s) - return false, "", nil -} - // Unimplemented functions. func (fd *netFD) dup() (*os.File, error) { // TODO: Implement this - return nil, os.NewSyscallError("dup", syscall.EWINDOWS) + return nil, syscall.EWINDOWS } -var errNoSupport = errors.New("address family not supported") - func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { - return 0, 0, 0, nil, errNoSupport + return 0, 0, 0, nil, syscall.EWINDOWS } func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { - return 0, 0, errNoSupport + return 0, 0, syscall.EWINDOWS } diff --git a/libgo/go/net/file.go b/libgo/go/net/file.go new file mode 100644 index 00000000000..1aad477400c --- /dev/null +++ b/libgo/go/net/file.go @@ -0,0 +1,48 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package net + +import "os" + +type fileAddr string + +func (fileAddr) Network() string { return "file+net" } +func (f fileAddr) String() string { return string(f) } + +// FileConn returns a copy of the network connection corresponding to +// the open file f. +// 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 FileConn(f *os.File) (c Conn, err error) { + c, err = fileConn(f) + if err != nil { + err = &OpError{Op: "file", Net: "file+net", Source: nil, Addr: fileAddr(f.Name()), Err: err} + } + return +} + +// FileListener returns a copy of the network listener corresponding +// to the open file f. +// It is the caller's responsibility to close ln when finished. +// Closing ln does not affect f, and closing f does not affect ln. +func FileListener(f *os.File) (ln Listener, err error) { + ln, err = fileListener(f) + if err != nil { + err = &OpError{Op: "file", Net: "file+net", Source: nil, Addr: fileAddr(f.Name()), Err: err} + } + return +} + +// FilePacketConn returns a copy of the packet network connection +// corresponding to the open file f. +// 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 FilePacketConn(f *os.File) (c PacketConn, err error) { + c, err = filePacketConn(f) + if err != nil { + err = &OpError{Op: "file", Net: "file+net", Source: nil, Addr: fileAddr(f.Name()), Err: err} + } + return +} diff --git a/libgo/go/net/file_plan9.go b/libgo/go/net/file_plan9.go index 068f0881dd3..892775a024f 100644 --- a/libgo/go/net/file_plan9.go +++ b/libgo/go/net/file_plan9.go @@ -86,7 +86,7 @@ func newFileFD(f *os.File) (net *netFD, err error) { return newFD(comp[1], name, ctl, nil, laddr, nil) } -func newFileConn(f *os.File) (c Conn, err error) { +func fileConn(f *os.File) (Conn, error) { fd, err := newFileFD(f) if err != nil { return nil, err @@ -109,7 +109,7 @@ func newFileConn(f *os.File) (c Conn, err error) { return nil, syscall.EPLAN9 } -func newFileListener(f *os.File) (l Listener, err error) { +func fileListener(f *os.File) (Listener, error) { fd, err := newFileFD(f) if err != nil { return nil, err @@ -132,26 +132,6 @@ func newFileListener(f *os.File) (l Listener, err error) { return &TCPListener{fd}, nil } -// FileConn returns a copy of the network connection corresponding to -// the open file f. 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 FileConn(f *os.File) (c Conn, err error) { - return newFileConn(f) -} - -// FileListener returns a copy of the network listener corresponding -// to the open file f. It is the caller's responsibility to close l -// when finished. Closing l does not affect f, and closing f does not -// affect l. -func FileListener(f *os.File) (l Listener, err error) { - return newFileListener(f) -} - -// FilePacketConn returns a copy of the packet network connection -// corresponding to the open file f. 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 FilePacketConn(f *os.File) (c PacketConn, err error) { +func filePacketConn(f *os.File) (PacketConn, error) { return nil, syscall.EPLAN9 } diff --git a/libgo/go/net/file_stub.go b/libgo/go/net/file_stub.go index 4281072ef93..0f7460c7579 100644 --- a/libgo/go/net/file_stub.go +++ b/libgo/go/net/file_stub.go @@ -11,28 +11,6 @@ import ( "syscall" ) -// FileConn returns a copy of the network connection corresponding to -// the open file f. 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 FileConn(f *os.File) (c Conn, err error) { - return nil, syscall.ENOPROTOOPT - -} - -// FileListener returns a copy of the network listener corresponding -// to the open file f. It is the caller's responsibility to close l -// when finished. Closing l does not affect f, and closing f does not -// affect l. -func FileListener(f *os.File) (l Listener, err error) { - return nil, syscall.ENOPROTOOPT - -} - -// FilePacketConn returns a copy of the packet network connection -// corresponding to the open file f. 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 FilePacketConn(f *os.File) (c PacketConn, err error) { - return nil, syscall.ENOPROTOOPT -} +func fileConn(f *os.File) (Conn, error) { return nil, syscall.ENOPROTOOPT } +func fileListener(f *os.File) (Listener, error) { return nil, syscall.ENOPROTOOPT } +func filePacketConn(f *os.File) (PacketConn, error) { return nil, syscall.ENOPROTOOPT } diff --git a/libgo/go/net/file_test.go b/libgo/go/net/file_test.go index 6fab06a9c6e..003dbb2ecb7 100644 --- a/libgo/go/net/file_test.go +++ b/libgo/go/net/file_test.go @@ -27,77 +27,69 @@ type connFile interface { } func testFileListener(t *testing.T, net, laddr string) { - switch net { - case "tcp", "tcp4", "tcp6": - laddr += ":0" // any available port - } l, err := Listen(net, laddr) if err != nil { - t.Fatalf("Listen failed: %v", err) + t.Fatal(err) } defer l.Close() lf := l.(listenerFile) f, err := lf.File() if err != nil { - t.Fatalf("File failed: %v", err) + t.Fatal(err) } c, err := FileListener(f) if err != nil { - t.Fatalf("FileListener failed: %v", err) + t.Fatal(err) } if !reflect.DeepEqual(l.Addr(), c.Addr()) { - t.Fatalf("Addrs not equal: %#v != %#v", l.Addr(), c.Addr()) + t.Fatalf("got %#v; want%#v", l.Addr(), c.Addr()) } if err := c.Close(); err != nil { - t.Fatalf("Close failed: %v", err) + t.Fatal(err) } if err := f.Close(); err != nil { - t.Fatalf("Close failed: %v", err) + t.Fatal(err) } } var fileListenerTests = []struct { net string laddr string - ipv6 bool // test with underlying AF_INET6 socket - linux bool // test with abstract unix domain socket, a Linux-ism }{ - {net: "tcp", laddr: ""}, - {net: "tcp", laddr: "0.0.0.0"}, - {net: "tcp", laddr: "[::ffff:0.0.0.0]"}, - {net: "tcp", laddr: "[::]", ipv6: true}, + {net: "tcp", laddr: ":0"}, + {net: "tcp", laddr: "0.0.0.0:0"}, + {net: "tcp", laddr: "[::ffff:0.0.0.0]:0"}, + {net: "tcp", laddr: "[::]:0"}, - {net: "tcp", laddr: "127.0.0.1"}, - {net: "tcp", laddr: "[::ffff:127.0.0.1]"}, - {net: "tcp", laddr: "[::1]", ipv6: true}, + {net: "tcp", laddr: "127.0.0.1:0"}, + {net: "tcp", laddr: "[::ffff:127.0.0.1]:0"}, + {net: "tcp", laddr: "[::1]:0"}, - {net: "tcp4", laddr: ""}, - {net: "tcp4", laddr: "0.0.0.0"}, - {net: "tcp4", laddr: "[::ffff:0.0.0.0]"}, + {net: "tcp4", laddr: ":0"}, + {net: "tcp4", laddr: "0.0.0.0:0"}, + {net: "tcp4", laddr: "[::ffff:0.0.0.0]:0"}, - {net: "tcp4", laddr: "127.0.0.1"}, - {net: "tcp4", laddr: "[::ffff:127.0.0.1]"}, + {net: "tcp4", laddr: "127.0.0.1:0"}, + {net: "tcp4", laddr: "[::ffff:127.0.0.1]:0"}, - {net: "tcp6", laddr: "", ipv6: true}, - {net: "tcp6", laddr: "[::]", ipv6: true}, + {net: "tcp6", laddr: ":0"}, + {net: "tcp6", laddr: "[::]:0"}, - {net: "tcp6", laddr: "[::1]", ipv6: true}, + {net: "tcp6", laddr: "[::1]:0"}, - {net: "unix", laddr: "@gotest/net", linux: true}, - {net: "unixpacket", laddr: "@gotest/net", linux: true}, + {net: "unix", laddr: "@gotest/net"}, + {net: "unixpacket", laddr: "@gotest/net"}, } func TestFileListener(t *testing.T) { switch runtime.GOOS { case "nacl", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } for _, tt := range fileListenerTests { - if skipServerTest(tt.net, "unix", tt.laddr, tt.ipv6, false, tt.linux) { - continue - } - if skipServerTest(tt.net, "unixpacket", tt.laddr, tt.ipv6, false, tt.linux) { + if !testableListenArgs(tt.net, tt.laddr, "") { + t.Logf("skipping %s test", tt.net+" "+tt.laddr) continue } testFileListener(t, tt.net, tt.laddr) @@ -107,86 +99,78 @@ func TestFileListener(t *testing.T) { func testFilePacketConn(t *testing.T, pcf packetConnFile, listen bool) { f, err := pcf.File() if err != nil { - t.Fatalf("File failed: %v", err) + t.Fatal(err) } c, err := FilePacketConn(f) if err != nil { - t.Fatalf("FilePacketConn failed: %v", err) + t.Fatal(err) } if !reflect.DeepEqual(pcf.LocalAddr(), c.LocalAddr()) { - t.Fatalf("LocalAddrs not equal: %#v != %#v", pcf.LocalAddr(), c.LocalAddr()) + t.Fatalf("got %#v; want %#v", pcf.LocalAddr(), c.LocalAddr()) } if listen { if _, err := c.WriteTo([]byte{}, c.LocalAddr()); err != nil { - t.Fatalf("WriteTo failed: %v", err) + t.Fatal(err) } } if err := c.Close(); err != nil { - t.Fatalf("Close failed: %v", err) + t.Fatal(err) } if err := f.Close(); err != nil { - t.Fatalf("Close failed: %v", err) + t.Fatal(err) } } func testFilePacketConnListen(t *testing.T, net, laddr string) { - switch net { - case "udp", "udp4", "udp6": - laddr += ":0" // any available port - } l, err := ListenPacket(net, laddr) if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } testFilePacketConn(t, l.(packetConnFile), true) if err := l.Close(); err != nil { - t.Fatalf("Close failed: %v", err) + t.Fatal(err) } } func testFilePacketConnDial(t *testing.T, net, raddr string) { - switch net { - case "udp", "udp4", "udp6": - raddr += ":12345" - } c, err := Dial(net, raddr) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } testFilePacketConn(t, c.(packetConnFile), false) if err := c.Close(); err != nil { - t.Fatalf("Close failed: %v", err) + t.Fatal(err) } } var filePacketConnTests = []struct { - net string - addr string - ipv6 bool // test with underlying AF_INET6 socket - linux bool // test with abstract unix domain socket, a Linux-ism + net string + addr string }{ - {net: "udp", addr: "127.0.0.1"}, - {net: "udp", addr: "[::ffff:127.0.0.1]"}, - {net: "udp", addr: "[::1]", ipv6: true}, + {net: "udp", addr: "127.0.0.1:0"}, + {net: "udp", addr: "[::ffff:127.0.0.1]:0"}, + {net: "udp", addr: "[::1]:0"}, - {net: "udp4", addr: "127.0.0.1"}, - {net: "udp4", addr: "[::ffff:127.0.0.1]"}, + {net: "udp4", addr: "127.0.0.1:0"}, + {net: "udp4", addr: "[::ffff:127.0.0.1]:0"}, - {net: "udp6", addr: "[::1]", ipv6: true}, + {net: "udp6", addr: "[::1]:0"}, - {net: "ip4:icmp", addr: "127.0.0.1"}, + // TODO(mikioh,bradfitz): reenable once 10730 is fixed + // {net: "ip4:icmp", addr: "127.0.0.1"}, - {net: "unixgram", addr: "@gotest3/net", linux: true}, + {net: "unixgram", addr: "@gotest3/net"}, } func TestFilePacketConn(t *testing.T) { switch runtime.GOOS { case "nacl", "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } for _, tt := range filePacketConnTests { - if skipServerTest(tt.net, "unixgram", tt.addr, tt.ipv6, false, tt.linux) { + if !testableListenArgs(tt.net, tt.addr, "") { + t.Logf("skipping %s test", tt.net+" "+tt.addr) continue } if os.Getuid() != 0 && tt.net == "ip4:icmp" { @@ -194,12 +178,16 @@ func TestFilePacketConn(t *testing.T) { continue } testFilePacketConnListen(t, tt.net, tt.addr) - switch tt.addr { - case "", "0.0.0.0", "[::ffff:0.0.0.0]", "[::]": - default: - if tt.net != "unixgram" { - testFilePacketConnDial(t, tt.net, tt.addr) + switch tt.net { + case "udp", "udp4", "udp6": + host, _, err := SplitHostPort(tt.addr) + if err != nil { + t.Error(err) + continue } + testFilePacketConnDial(t, tt.net, JoinHostPort(host, "12345")) + case "ip4:icmp": + testFilePacketConnDial(t, tt.net, tt.addr) } } } diff --git a/libgo/go/net/file_unix.go b/libgo/go/net/file_unix.go index 214a4196c8e..5b24c7d09d1 100644 --- a/libgo/go/net/file_unix.go +++ b/libgo/go/net/file_unix.go @@ -11,75 +11,59 @@ import ( "syscall" ) -func newFileFD(f *os.File) (*netFD, error) { - fd, err := dupCloseOnExec(int(f.Fd())) +func dupSocket(f *os.File) (int, error) { + s, err := dupCloseOnExec(int(f.Fd())) if err != nil { - return nil, os.NewSyscallError("dup", err) + return -1, err + } + if err := syscall.SetNonblock(s, true); err != nil { + closeFunc(s) + return -1, os.NewSyscallError("setnonblock", err) } + return s, nil +} - if err = syscall.SetNonblock(fd, true); err != nil { - closesocket(fd) +func newFileFD(f *os.File) (*netFD, error) { + s, err := dupSocket(f) + if err != nil { return nil, err } - - sotype, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TYPE) + family := syscall.AF_UNSPEC + sotype, err := syscall.GetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_TYPE) if err != nil { - closesocket(fd) + closeFunc(s) return nil, os.NewSyscallError("getsockopt", err) } - - family := syscall.AF_UNSPEC - toAddr := sockaddrToTCP - lsa, _ := syscall.Getsockname(fd) + lsa, _ := syscall.Getsockname(s) + rsa, _ := syscall.Getpeername(s) switch lsa.(type) { - default: - closesocket(fd) - return nil, syscall.EINVAL case *syscall.SockaddrInet4: family = syscall.AF_INET - if sotype == syscall.SOCK_DGRAM { - toAddr = sockaddrToUDP - } else if sotype == syscall.SOCK_RAW { - toAddr = sockaddrToIP - } case *syscall.SockaddrInet6: family = syscall.AF_INET6 - if sotype == syscall.SOCK_DGRAM { - toAddr = sockaddrToUDP - } else if sotype == syscall.SOCK_RAW { - toAddr = sockaddrToIP - } case *syscall.SockaddrUnix: family = syscall.AF_UNIX - toAddr = sockaddrToUnix - if sotype == syscall.SOCK_DGRAM { - toAddr = sockaddrToUnixgram - } else if sotype == syscall.SOCK_SEQPACKET { - toAddr = sockaddrToUnixpacket - } + default: + closeFunc(s) + return nil, syscall.EPROTONOSUPPORT } - laddr := toAddr(lsa) - rsa, _ := syscall.Getpeername(fd) - raddr := toAddr(rsa) - - netfd, err := newFD(fd, family, sotype, laddr.Network()) + fd, err := newFD(s, family, sotype, "") if err != nil { - closesocket(fd) + closeFunc(s) return nil, err } - if err := netfd.init(); err != nil { - netfd.Close() + laddr := fd.addrFunc()(lsa) + raddr := fd.addrFunc()(rsa) + fd.net = laddr.Network() + if err := fd.init(); err != nil { + fd.Close() return nil, err } - netfd.setAddr(laddr, raddr) - return netfd, nil + fd.setAddr(laddr, raddr) + return fd, nil } -// FileConn returns a copy of the network connection corresponding to -// the open file f. 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 FileConn(f *os.File) (c Conn, err error) { +func fileConn(f *os.File) (Conn, error) { fd, err := newFileFD(f) if err != nil { return nil, err @@ -98,11 +82,7 @@ func FileConn(f *os.File) (c Conn, err error) { return nil, syscall.EINVAL } -// FileListener returns a copy of the network listener corresponding -// to the open file f. It is the caller's responsibility to close l -// when finished. Closing l does not affect f, and closing f does not -// affect l. -func FileListener(f *os.File) (l Listener, err error) { +func fileListener(f *os.File) (Listener, error) { fd, err := newFileFD(f) if err != nil { return nil, err @@ -117,11 +97,7 @@ func FileListener(f *os.File) (l Listener, err error) { return nil, syscall.EINVAL } -// FilePacketConn returns a copy of the packet network connection -// corresponding to the open file f. 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 FilePacketConn(f *os.File) (c PacketConn, err error) { +func filePacketConn(f *os.File) (PacketConn, error) { fd, err := newFileFD(f) if err != nil { return nil, err diff --git a/libgo/go/net/file_windows.go b/libgo/go/net/file_windows.go index ca2b9b2262c..241fa17617c 100644 --- a/libgo/go/net/file_windows.go +++ b/libgo/go/net/file_windows.go @@ -9,29 +9,17 @@ import ( "syscall" ) -// FileConn returns a copy of the network connection corresponding to -// the open file f. 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 FileConn(f *os.File) (c Conn, err error) { +func fileConn(f *os.File) (Conn, error) { // TODO: Implement this - return nil, os.NewSyscallError("FileConn", syscall.EWINDOWS) + return nil, syscall.EWINDOWS } -// FileListener returns a copy of the network listener corresponding -// to the open file f. It is the caller's responsibility to close l -// when finished. Closing l does not affect f, and closing f does not -// affect l. -func FileListener(f *os.File) (l Listener, err error) { +func fileListener(f *os.File) (Listener, error) { // TODO: Implement this - return nil, os.NewSyscallError("FileListener", syscall.EWINDOWS) + return nil, syscall.EWINDOWS } -// FilePacketConn returns a copy of the packet network connection -// corresponding to the open file f. 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 FilePacketConn(f *os.File) (c PacketConn, err error) { +func filePacketConn(f *os.File) (PacketConn, error) { // TODO: Implement this - return nil, os.NewSyscallError("FilePacketConn", syscall.EWINDOWS) + return nil, syscall.EWINDOWS } diff --git a/libgo/go/net/hook.go b/libgo/go/net/hook.go new file mode 100644 index 00000000000..9ab34c0e36f --- /dev/null +++ b/libgo/go/net/hook.go @@ -0,0 +1,12 @@ +// Copyright 2015 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 + +var ( + testHookDialTCP = dialTCP + testHookHostsPath = "/etc/hosts" + testHookLookupIP = func(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { return fn(host) } + testHookSetKeepAlive = func() {} +) diff --git a/libgo/go/net/hook_cloexec.go b/libgo/go/net/hook_cloexec.go new file mode 100644 index 00000000000..870f0d78b12 --- /dev/null +++ b/libgo/go/net/hook_cloexec.go @@ -0,0 +1,14 @@ +// Copyright 2015 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 freebsd linux + +package net + +import "syscall" + +var ( + // Placeholders for socket system calls. + accept4Func func(int, int) (int, syscall.Sockaddr, error) = syscall.Accept4 +) diff --git a/libgo/go/net/hook_plan9.go b/libgo/go/net/hook_plan9.go new file mode 100644 index 00000000000..e053348505b --- /dev/null +++ b/libgo/go/net/hook_plan9.go @@ -0,0 +1,9 @@ +// Copyright 2015 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 "time" + +var testHookDialChannel = func() { time.Sleep(time.Millisecond) } // see golang.org/issue/5349 diff --git a/libgo/go/net/hook_unix.go b/libgo/go/net/hook_unix.go new file mode 100644 index 00000000000..361ca5980c3 --- /dev/null +++ b/libgo/go/net/hook_unix.go @@ -0,0 +1,21 @@ +// Copyright 2015 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 dragonfly freebsd linux nacl netbsd openbsd solaris + +package net + +import "syscall" + +var ( + testHookDialChannel = func() {} // see golang.org/issue/5349 + + // Placeholders for socket system calls. + socketFunc func(int, int, int) (int, error) = syscall.Socket + closeFunc func(int) error = syscall.Close + connectFunc func(int, syscall.Sockaddr) error = syscall.Connect + listenFunc func(int, int) error = syscall.Listen + acceptFunc func(int) (int, syscall.Sockaddr, error) = syscall.Accept + getsockoptIntFunc func(int, int, int) (int, error) = syscall.GetsockoptInt +) diff --git a/libgo/go/net/hook_windows.go b/libgo/go/net/hook_windows.go new file mode 100644 index 00000000000..126b0ebdd10 --- /dev/null +++ b/libgo/go/net/hook_windows.go @@ -0,0 +1,21 @@ +// Copyright 2015 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 ( + "syscall" + "time" +) + +var ( + testHookDialChannel = func() { time.Sleep(time.Millisecond) } // see golang.org/issue/5349 + + // Placeholders for socket system calls. + socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket + closeFunc func(syscall.Handle) error = syscall.Closesocket + connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect + connectExFunc func(syscall.Handle, syscall.Sockaddr, *byte, uint32, *uint32, *syscall.Overlapped) error = syscall.ConnectEx + listenFunc func(syscall.Handle, int) error = syscall.Listen +) diff --git a/libgo/go/net/hosts.go b/libgo/go/net/hosts.go index 9400503e41e..27958c7cc50 100644 --- a/libgo/go/net/hosts.go +++ b/libgo/go/net/hosts.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Read static host/IP entries from /etc/hosts. - package net import ( @@ -13,8 +11,21 @@ import ( const cacheMaxAge = 5 * time.Minute -// hostsPath points to the file with static IP/address entries. -var hostsPath = "/etc/hosts" +func parseLiteralIP(addr string) string { + var ip IP + var zone string + ip = parseIPv4(addr) + if ip == nil { + ip, zone = parseIPv6(addr, true) + } + if ip == nil { + return "" + } + if zone == "" { + return ip.String() + } + return ip.String() + "%" + zone +} // Simple cache. var hosts struct { @@ -27,7 +38,7 @@ var hosts struct { func readHosts() { now := time.Now() - hp := hostsPath + hp := testHookHostsPath if len(hosts.byName) == 0 || now.After(hosts.expire) || hosts.path != hp { hs := make(map[string][]string) is := make(map[string][]string) @@ -41,13 +52,17 @@ func readHosts() { line = line[0:i] } f := getFields(line) - if len(f) < 2 || ParseIP(f[0]) == nil { + if len(f) < 2 { + continue + } + addr := parseLiteralIP(f[0]) + if addr == "" { continue } for i := 1; i < len(f); i++ { h := f[i] - hs[h] = append(hs[h], f[0]) - is[f[0]] = append(is[f[0]], h) + hs[h] = append(hs[h], addr) + is[addr] = append(is[addr], h) } } // Update the data cache. @@ -77,6 +92,10 @@ func lookupStaticAddr(addr string) []string { hosts.Lock() defer hosts.Unlock() readHosts() + addr = parseLiteralIP(addr) + if addr == "" { + return nil + } if len(hosts.byAddr) != 0 { if hosts, ok := hosts.byAddr[addr]; ok { return hosts diff --git a/libgo/go/net/hosts_test.go b/libgo/go/net/hosts_test.go index 2fe358e079c..aca64c38b05 100644 --- a/libgo/go/net/hosts_test.go +++ b/libgo/go/net/hosts_test.go @@ -5,77 +5,116 @@ package net import ( - "sort" + "reflect" "testing" ) -type hostTest struct { - host string - ips []IP +type staticHostEntry struct { + in string + out []string } -var hosttests = []hostTest{ - {"odin", []IP{ - IPv4(127, 0, 0, 2), - IPv4(127, 0, 0, 3), - ParseIP("::2"), - }}, - {"thor", []IP{ - IPv4(127, 1, 1, 1), - }}, - {"loki", []IP{}}, - {"ullr", []IP{ - IPv4(127, 1, 1, 2), - }}, - {"ullrhost", []IP{ - IPv4(127, 1, 1, 2), - }}, +var lookupStaticHostTests = []struct { + name string + ents []staticHostEntry +}{ + { + "testdata/hosts", + []staticHostEntry{ + {"odin", []string{"127.0.0.2", "127.0.0.3", "::2"}}, + {"thor", []string{"127.1.1.1"}}, + {"ullr", []string{"127.1.1.2"}}, + {"ullrhost", []string{"127.1.1.2"}}, + {"localhost", []string{"fe80::1%lo0"}}, + }, + }, + { + "testdata/singleline-hosts", // see golang.org/issue/6646 + []staticHostEntry{ + {"odin", []string{"127.0.0.2"}}, + }, + }, + { + "testdata/ipv4-hosts", // see golang.org/issue/8996 + []staticHostEntry{ + {"localhost", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}}, + {"localhost.localdomain", []string{"127.0.0.3"}}, + }, + }, + { + "testdata/ipv6-hosts", // see golang.org/issue/8996 + []staticHostEntry{ + {"localhost", []string{"::1", "fe80::1", "fe80::2%lo0", "fe80::3%lo0"}}, + {"localhost.localdomain", []string{"fe80::3%lo0"}}, + }, + }, } func TestLookupStaticHost(t *testing.T) { - p := hostsPath - hostsPath = "testdata/hosts" - for i := 0; i < len(hosttests); i++ { - tt := hosttests[i] - ips := lookupStaticHost(tt.host) - if len(ips) != len(tt.ips) { - t.Errorf("# of hosts = %v; want %v", - len(ips), len(tt.ips)) - continue - } - for k, v := range ips { - if tt.ips[k].String() != v { - t.Errorf("lookupStaticHost(%q) = %v; want %v", - tt.host, v, tt.ips[k]) + defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) + + for _, tt := range lookupStaticHostTests { + testHookHostsPath = tt.name + for _, ent := range tt.ents { + addrs := lookupStaticHost(ent.in) + if !reflect.DeepEqual(addrs, ent.out) { + t.Errorf("%s, lookupStaticHost(%s) = %v; want %v", tt.name, ent.in, addrs, ent.out) } } } - hostsPath = p } -// https://code.google.com/p/go/issues/detail?id=6646 -func TestSingleLineHostsFile(t *testing.T) { - p := hostsPath - hostsPath = "testdata/hosts_singleline" - - ips := lookupStaticHost("odin") - if len(ips) != 1 || ips[0] != "127.0.0.2" { - t.Errorf("lookupStaticHost = %v, want %v", ips, []string{"127.0.0.2"}) - } - - hostsPath = p +var lookupStaticAddrTests = []struct { + name string + ents []staticHostEntry +}{ + { + "testdata/hosts", + []staticHostEntry{ + {"255.255.255.255", []string{"broadcasthost"}}, + {"127.0.0.2", []string{"odin"}}, + {"127.0.0.3", []string{"odin"}}, + {"::2", []string{"odin"}}, + {"127.1.1.1", []string{"thor"}}, + {"127.1.1.2", []string{"ullr", "ullrhost"}}, + {"fe80::1%lo0", []string{"localhost"}}, + }, + }, + { + "testdata/singleline-hosts", // see golang.org/issue/6646 + []staticHostEntry{ + {"127.0.0.2", []string{"odin"}}, + }, + }, + { + "testdata/ipv4-hosts", // see golang.org/issue/8996 + []staticHostEntry{ + {"127.0.0.1", []string{"localhost"}}, + {"127.0.0.2", []string{"localhost"}}, + {"127.0.0.3", []string{"localhost", "localhost.localdomain"}}, + }, + }, + { + "testdata/ipv6-hosts", // see golang.org/issue/8996 + []staticHostEntry{ + {"::1", []string{"localhost"}}, + {"fe80::1", []string{"localhost"}}, + {"fe80::2%lo0", []string{"localhost"}}, + {"fe80::3%lo0", []string{"localhost", "localhost.localdomain"}}, + }, + }, } -func TestLookupHost(t *testing.T) { - // Can't depend on this to return anything in particular, - // but if it does return something, make sure it doesn't - // duplicate addresses (a common bug due to the way - // getaddrinfo works). - addrs, _ := LookupHost("localhost") - sort.Strings(addrs) - for i := 0; i+1 < len(addrs); i++ { - if addrs[i] == addrs[i+1] { - t.Fatalf("LookupHost(\"localhost\") = %v, has duplicate addresses", addrs) +func TestLookupStaticAddr(t *testing.T) { + defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) + + for _, tt := range lookupStaticAddrTests { + testHookHostsPath = tt.name + for _, ent := range tt.ents { + hosts := lookupStaticAddr(ent.in) + if !reflect.DeepEqual(hosts, ent.out) { + t.Errorf("%s, lookupStaticAddr(%s) = %v; want %v", tt.name, ent.in, hosts, ent.out) + } } } } diff --git a/libgo/go/net/http/cgi/child.go b/libgo/go/net/http/cgi/child.go index 45fc2e57cd7..ec101088219 100644 --- a/libgo/go/net/http/cgi/child.go +++ b/libgo/go/net/http/cgi/child.go @@ -132,9 +132,9 @@ func RequestFromMap(params map[string]string) (*http.Request, error) { } // Request.RemoteAddr has its port set by Go's standard http - // server, so we do here too. We don't have one, though, so we - // use a dummy one. - r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], "0") + // server, so we do here too. + remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid + r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort)) return r, nil } diff --git a/libgo/go/net/http/cgi/child_test.go b/libgo/go/net/http/cgi/child_test.go index 075d8411bcf..14e0af475f5 100644 --- a/libgo/go/net/http/cgi/child_test.go +++ b/libgo/go/net/http/cgi/child_test.go @@ -22,6 +22,7 @@ func TestRequest(t *testing.T) { "CONTENT_LENGTH": "123", "CONTENT_TYPE": "text/xml", "REMOTE_ADDR": "5.6.7.8", + "REMOTE_PORT": "54321", } req, err := RequestFromMap(env) if err != nil { @@ -60,7 +61,7 @@ func TestRequest(t *testing.T) { if req.TLS != nil { t.Errorf("expected nil TLS") } - if e, g := "5.6.7.8:0", req.RemoteAddr; e != g { + if e, g := "5.6.7.8:54321", req.RemoteAddr; e != g { t.Errorf("RemoteAddr: got %q; want %q", g, e) } } @@ -129,3 +130,21 @@ func TestRequestWithoutRequestURI(t *testing.T) { t.Errorf("URL = %q; want %q", g, e) } } + +func TestRequestWithoutRemotePort(t *testing.T) { + env := map[string]string{ + "SERVER_PROTOCOL": "HTTP/1.1", + "HTTP_HOST": "example.com", + "REQUEST_METHOD": "GET", + "REQUEST_URI": "/path?a=b", + "CONTENT_LENGTH": "123", + "REMOTE_ADDR": "5.6.7.8", + } + req, err := RequestFromMap(env) + if err != nil { + t.Fatalf("RequestFromMap: %v", err) + } + if e, g := "5.6.7.8:0", req.RemoteAddr; e != g { + t.Errorf("RemoteAddr: got %q; want %q", g, e) + } +} diff --git a/libgo/go/net/http/cgi/host.go b/libgo/go/net/http/cgi/host.go index ec95a972c1a..4efbe7abeec 100644 --- a/libgo/go/net/http/cgi/host.go +++ b/libgo/go/net/http/cgi/host.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "log" + "net" "net/http" "os" "os/exec" @@ -128,11 +129,16 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { "PATH_INFO=" + pathInfo, "SCRIPT_NAME=" + root, "SCRIPT_FILENAME=" + h.Path, - "REMOTE_ADDR=" + req.RemoteAddr, - "REMOTE_HOST=" + req.RemoteAddr, "SERVER_PORT=" + port, } + if remoteIP, remotePort, err := net.SplitHostPort(req.RemoteAddr); err == nil { + env = append(env, "REMOTE_ADDR="+remoteIP, "REMOTE_HOST="+remoteIP, "REMOTE_PORT="+remotePort) + } else { + // could not parse ip:port, let's use whole RemoteAddr and leave REMOTE_PORT undefined + env = append(env, "REMOTE_ADDR="+req.RemoteAddr, "REMOTE_HOST="+req.RemoteAddr) + } + if req.TLS != nil { env = append(env, "HTTPS=on") } diff --git a/libgo/go/net/http/cgi/host_test.go b/libgo/go/net/http/cgi/host_test.go index b514e10e963..f3411105ca9 100644 --- a/libgo/go/net/http/cgi/host_test.go +++ b/libgo/go/net/http/cgi/host_test.go @@ -29,7 +29,7 @@ func newRequest(httpreq string) *http.Request { if err != nil { panic("cgi: bogus http request in test: " + httpreq) } - req.RemoteAddr = "1.2.3.4" + req.RemoteAddr = "1.2.3.4:1234" return req } @@ -37,7 +37,11 @@ func runCgiTest(t *testing.T, h *Handler, httpreq string, expectedMap map[string rw := httptest.NewRecorder() req := newRequest(httpreq) h.ServeHTTP(rw, req) + runResponseChecks(t, rw, expectedMap) + return rw +} +func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder, expectedMap map[string]string) { // Make a map to hold the test map that the CGI returns. m := make(map[string]string) m["_body"] = rw.Body.String() @@ -75,7 +79,6 @@ readlines: t.Errorf("for key %q got %q; expected %q", key, got, expected) } } - return rw } var cgiTested, cgiWorks bool @@ -108,6 +111,7 @@ func TestCGIBasicGet(t *testing.T) { "env-QUERY_STRING": "foo=bar&a=b", "env-REMOTE_ADDR": "1.2.3.4", "env-REMOTE_HOST": "1.2.3.4", + "env-REMOTE_PORT": "1234", "env-REQUEST_METHOD": "GET", "env-REQUEST_URI": "/test.cgi?foo=bar&a=b", "env-SCRIPT_FILENAME": "testdata/test.cgi", @@ -126,6 +130,39 @@ func TestCGIBasicGet(t *testing.T) { } } +func TestCGIEnvIPv6(t *testing.T) { + check(t) + h := &Handler{ + Path: "testdata/test.cgi", + Root: "/test.cgi", + } + expectedMap := map[string]string{ + "test": "Hello CGI", + "param-a": "b", + "param-foo": "bar", + "env-GATEWAY_INTERFACE": "CGI/1.1", + "env-HTTP_HOST": "example.com", + "env-PATH_INFO": "", + "env-QUERY_STRING": "foo=bar&a=b", + "env-REMOTE_ADDR": "2000::3000", + "env-REMOTE_HOST": "2000::3000", + "env-REMOTE_PORT": "12345", + "env-REQUEST_METHOD": "GET", + "env-REQUEST_URI": "/test.cgi?foo=bar&a=b", + "env-SCRIPT_FILENAME": "testdata/test.cgi", + "env-SCRIPT_NAME": "/test.cgi", + "env-SERVER_NAME": "example.com", + "env-SERVER_PORT": "80", + "env-SERVER_SOFTWARE": "go", + } + + rw := httptest.NewRecorder() + req := newRequest("GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n") + req.RemoteAddr = "[2000::3000]:12345" + h.ServeHTTP(rw, req) + runResponseChecks(t, rw, expectedMap) +} + func TestCGIBasicGetAbsPath(t *testing.T) { check(t) pwd, err := os.Getwd() @@ -289,7 +326,7 @@ func TestInternalRedirect(t *testing.T) { } expectedMap := map[string]string{ "basepath": "/foo", - "remoteaddr": "1.2.3.4", + "remoteaddr": "1.2.3.4:1234", } runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap) } diff --git a/libgo/go/net/http/cgi/matryoshka_test.go b/libgo/go/net/http/cgi/matryoshka_test.go index 18c4803e71b..32d59c09a3c 100644 --- a/libgo/go/net/http/cgi/matryoshka_test.go +++ b/libgo/go/net/http/cgi/matryoshka_test.go @@ -12,11 +12,11 @@ import ( "bytes" "errors" "fmt" + "internal/testenv" "io" "net/http" "net/http/httptest" "os" - "runtime" "testing" "time" ) @@ -24,9 +24,7 @@ import ( // This test is a CGI host (testing host.go) that runs its own binary // as a child process testing the other half of CGI (child.go). func TestHostingOurselves(t *testing.T) { - if runtime.GOOS == "nacl" { - t.Skip("skipping on nacl") - } + testenv.MustHaveExec(t) h := &Handler{ Path: os.Args[0], @@ -43,6 +41,7 @@ func TestHostingOurselves(t *testing.T) { "env-QUERY_STRING": "foo=bar&a=b", "env-REMOTE_ADDR": "1.2.3.4", "env-REMOTE_HOST": "1.2.3.4", + "env-REMOTE_PORT": "1234", "env-REQUEST_METHOD": "GET", "env-REQUEST_URI": "/test.go?foo=bar&a=b", "env-SCRIPT_FILENAME": os.Args[0], @@ -92,9 +91,7 @@ func (w *limitWriter) Write(p []byte) (n int, err error) { // If there's an error copying the child's output to the parent, test // that we kill the child. func TestKillChildAfterCopyError(t *testing.T) { - if runtime.GOOS == "nacl" { - t.Skip("skipping on nacl") - } + testenv.MustHaveExec(t) defer func() { testHookStartProcess = nil }() proc := make(chan *os.Process, 1) @@ -139,9 +136,7 @@ func TestKillChildAfterCopyError(t *testing.T) { // Test that a child handler writing only headers works. // golang.org/issue/7196 func TestChildOnlyHeaders(t *testing.T) { - if runtime.GOOS == "nacl" { - t.Skip("skipping on nacl") - } + testenv.MustHaveExec(t) h := &Handler{ Path: os.Args[0], diff --git a/libgo/go/net/http/cgi/testdata/test.cgi b/libgo/go/net/http/cgi/testdata/test.cgi index 3214df6f004..ec7ee6f3864 100644 --- a/libgo/go/net/http/cgi/testdata/test.cgi +++ b/libgo/go/net/http/cgi/testdata/test.cgi @@ -45,7 +45,7 @@ foreach my $k (sort keys %ENV) { # NOTE: msys perl returns /c/go/src/... not C:\go\.... my $dir = getcwd(); -if ($^O eq 'MSWin32' || $^O eq 'msys') { +if ($^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin') { if ($dir =~ /^.:/) { $dir =~ s!/!\\!g; } else { diff --git a/libgo/go/net/http/client.go b/libgo/go/net/http/client.go index ce884d1f07b..7f2fbb4678e 100644 --- a/libgo/go/net/http/client.go +++ b/libgo/go/net/http/client.go @@ -19,6 +19,7 @@ import ( "net/url" "strings" "sync" + "sync/atomic" "time" ) @@ -211,7 +212,7 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) { req.Header = make(Header) } - if u := req.URL.User; u != nil { + if u := req.URL.User; u != nil && req.Header.Get("Authorization") == "" { username := u.Username() password, _ := u.Password() req.Header.Set("Authorization", "Basic "+basicAuth(username, password)) @@ -256,8 +257,9 @@ func shouldRedirectPost(statusCode int) bool { return false } -// Get issues a GET to the specified URL. If the response is one of the following -// redirect codes, Get follows the redirect, up to a maximum of 10 redirects: +// Get issues a GET to the specified URL. If the response is one of +// the following redirect codes, Get follows the redirect, up to a +// maximum of 10 redirects: // // 301 (Moved Permanently) // 302 (Found) @@ -272,13 +274,16 @@ func shouldRedirectPost(statusCode int) bool { // Caller should close resp.Body when done reading from it. // // Get is a wrapper around DefaultClient.Get. +// +// To make a request with custom headers, use NewRequest and +// DefaultClient.Do. func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url) } -// Get issues a GET to the specified URL. If the response is one of the +// Get issues a GET to the specified URL. If the response is one of the // following redirect codes, Get follows the redirect after calling the -// Client's CheckRedirect function. +// Client's CheckRedirect function: // // 301 (Moved Permanently) // 302 (Found) @@ -291,6 +296,8 @@ func Get(url string) (resp *Response, err error) { // // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. +// +// To make a request with custom headers, use NewRequest and Client.Do. func (c *Client) Get(url string) (resp *Response, err error) { req, err := NewRequest("GET", url, nil) if err != nil { @@ -299,6 +306,8 @@ func (c *Client) Get(url string) (resp *Response, err error) { return c.doFollowingRedirects(req, shouldRedirectGet) } +func alwaysFalse() bool { return false } + func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bool) (resp *Response, err error) { var base *url.URL redirectChecker := c.CheckRedirect @@ -316,7 +325,10 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo req := ireq var timer *time.Timer + var atomicWasCanceled int32 // atomic bool (1 or 0) + var wasCanceled = alwaysFalse if c.Timeout > 0 { + wasCanceled = func() bool { return atomic.LoadInt32(&atomicWasCanceled) != 0 } type canceler interface { CancelRequest(*Request) } @@ -325,6 +337,7 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo return nil, fmt.Errorf("net/http: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", c.transport()) } timer = time.AfterFunc(c.Timeout, func() { + atomic.StoreInt32(&atomicWasCanceled, 1) reqmu.Lock() defer reqmu.Unlock() tr.CancelRequest(req) @@ -365,6 +378,12 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo urlStr = req.URL.String() if resp, err = c.send(req); err != nil { + if wasCanceled() { + err = &httpError{ + err: err.Error() + " (Client.Timeout exceeded while awaiting headers)", + timeout: true, + } + } break } @@ -377,7 +396,7 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo } resp.Body.Close() if urlStr = resp.Header.Get("Location"); urlStr == "" { - err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode)) + err = fmt.Errorf("%d response missing Location header", resp.StatusCode) break } base = req.URL @@ -385,7 +404,11 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo continue } if timer != nil { - resp.Body = &cancelTimerBody{timer, resp.Body} + resp.Body = &cancelTimerBody{ + t: timer, + rc: resp.Body, + reqWasCanceled: wasCanceled, + } } return resp, nil } @@ -400,7 +423,7 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo if redirectFailed { // Special case for Go 1 compatibility: return both the response // and an error if the CheckRedirect function failed. - // See http://golang.org/issue/3795 + // See https://golang.org/issue/3795 return resp, urlErr } @@ -421,7 +444,12 @@ func defaultCheckRedirect(req *Request, via []*Request) error { // // Caller should close resp.Body when done reading from it. // -// Post is a wrapper around DefaultClient.Post +// If the provided body is an io.Closer, it is closed after the +// request. +// +// Post is a wrapper around DefaultClient.Post. +// +// To set custom headers, use NewRequest and DefaultClient.Do. func Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { return DefaultClient.Post(url, bodyType, body) } @@ -430,8 +458,10 @@ func Post(url string, bodyType string, body io.Reader) (resp *Response, err erro // // Caller should close resp.Body when done reading from it. // -// If the provided body is also an io.Closer, it is closed after the +// If the provided body is an io.Closer, it is closed after the // request. +// +// To set custom headers, use NewRequest and Client.Do. func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { req, err := NewRequest("POST", url, body) if err != nil { @@ -444,16 +474,22 @@ func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Respon // PostForm issues a POST to the specified URL, with data's keys and // values URL-encoded as the request body. // +// The Content-Type header is set to application/x-www-form-urlencoded. +// To set other headers, use NewRequest and DefaultClient.Do. +// // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. // -// PostForm is a wrapper around DefaultClient.PostForm +// PostForm is a wrapper around DefaultClient.PostForm. func PostForm(url string, data url.Values) (resp *Response, err error) { return DefaultClient.PostForm(url, data) } // PostForm issues a POST to the specified URL, -// with data's keys and values urlencoded as the request body. +// with data's keys and values URL-encoded as the request body. +// +// The Content-Type header is set to application/x-www-form-urlencoded. +// To set other headers, use NewRequest and DefaultClient.Do. // // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. @@ -461,9 +497,9 @@ func (c *Client) PostForm(url string, data url.Values) (resp *Response, err erro return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } -// Head issues a HEAD to the specified URL. If the response is one of the -// following redirect codes, Head follows the redirect after calling the -// Client's CheckRedirect function. +// Head issues a HEAD to the specified URL. If the response is one of +// the following redirect codes, Head follows the redirect, up to a +// maximum of 10 redirects: // // 301 (Moved Permanently) // 302 (Found) @@ -477,7 +513,7 @@ func Head(url string) (resp *Response, err error) { // Head issues a HEAD to the specified URL. If the response is one of the // following redirect codes, Head follows the redirect after calling the -// Client's CheckRedirect function. +// Client's CheckRedirect function: // // 301 (Moved Permanently) // 302 (Found) @@ -491,15 +527,25 @@ func (c *Client) Head(url string) (resp *Response, err error) { return c.doFollowingRedirects(req, shouldRedirectGet) } +// cancelTimerBody is an io.ReadCloser that wraps rc with two features: +// 1) on Read EOF or Close, the timer t is Stopped, +// 2) On Read failure, if reqWasCanceled is true, the error is wrapped and +// marked as net.Error that hit its timeout. type cancelTimerBody struct { - t *time.Timer - rc io.ReadCloser + t *time.Timer + rc io.ReadCloser + reqWasCanceled func() bool } func (b *cancelTimerBody) Read(p []byte) (n int, err error) { n, err = b.rc.Read(p) if err == io.EOF { b.t.Stop() + } else if err != nil && b.reqWasCanceled() { + return n, &httpError{ + err: err.Error() + " (Client.Timeout exceeded while reading body)", + timeout: true, + } } return } diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index 56b6563c486..7b524d381bc 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -258,7 +258,7 @@ func TestClientRedirects(t *testing.T) { t.Errorf("with redirects forbidden, expected a *url.Error with our 'no redirects allowed' error inside; got %#v (%q)", err, err) } if res == nil { - t.Fatalf("Expected a non-nil Response on CheckRedirect failure (http://golang.org/issue/3795)") + t.Fatalf("Expected a non-nil Response on CheckRedirect failure (https://golang.org/issue/3795)") } res.Body.Close() if res.Header.Get("Location") == "" { @@ -334,6 +334,7 @@ var echoCookiesRedirectHandler = HandlerFunc(func(w ResponseWriter, r *Request) }) func TestClientSendsCookieFromJar(t *testing.T) { + defer afterTest(t) tr := &recordingTransport{} client := &Client{Transport: tr} client.Jar = &TestJar{perURL: make(map[string][]*Cookie)} @@ -426,7 +427,7 @@ func TestJarCalls(t *testing.T) { ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { pathSuffix := r.RequestURI[1:] if r.RequestURI == "/nosetcookie" { - return // dont set cookies for this path + return // don't set cookies for this path } SetCookie(w, &Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix}) if r.RequestURI == "/" { @@ -738,7 +739,7 @@ func TestResponseSetsTLSConnectionState(t *testing.T) { } } -// Verify Response.ContentLength is populated. http://golang.org/issue/4126 +// Verify Response.ContentLength is populated. https://golang.org/issue/4126 func TestClientHeadContentLength(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -842,6 +843,47 @@ func TestBasicAuth(t *testing.T) { } } +func TestBasicAuthHeadersPreserved(t *testing.T) { + defer afterTest(t) + tr := &recordingTransport{} + client := &Client{Transport: tr} + + // If Authorization header is provided, username in URL should not override it + url := "http://My%20User@dummy.faketld/" + req, err := NewRequest("GET", url, nil) + if err != nil { + t.Fatal(err) + } + req.SetBasicAuth("My User", "My Pass") + expected := "My User:My Pass" + client.Do(req) + + if tr.req.Method != "GET" { + t.Errorf("got method %q, want %q", tr.req.Method, "GET") + } + if tr.req.URL.String() != url { + t.Errorf("got URL %q, want %q", tr.req.URL.String(), url) + } + if tr.req.Header == nil { + t.Fatalf("expected non-nil request Header") + } + auth := tr.req.Header.Get("Authorization") + if strings.HasPrefix(auth, "Basic ") { + encoded := auth[6:] + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + t.Fatal(err) + } + s := string(decoded) + if expected != s { + t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected) + } + } else { + t.Errorf("Invalid auth %q", auth) + } + +} + func TestClientTimeout(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") @@ -899,14 +941,64 @@ func TestClientTimeout(t *testing.T) { select { case err := <-errc: if err == nil { - t.Error("expected error from ReadAll") + t.Fatal("expected error from ReadAll") + } + ne, ok := err.(net.Error) + if !ok { + t.Errorf("error value from ReadAll was %T; expected some net.Error", err) + } else if !ne.Timeout() { + t.Errorf("net.Error.Timeout = false; want true") + } + if got := ne.Error(); !strings.Contains(got, "Client.Timeout exceeded") { + t.Errorf("error string = %q; missing timeout substring", got) } - // Expected error. case <-time.After(failTime): t.Errorf("timeout after %v waiting for timeout of %v", failTime, timeout) } } +// Client.Timeout firing before getting to the body +func TestClientTimeout_Headers(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + defer afterTest(t) + donec := make(chan bool) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + <-donec + })) + defer ts.Close() + // Note that we use a channel send here and not a close. + // The race detector doesn't know that we're waiting for a timeout + // and thinks that the waitgroup inside httptest.Server is added to concurrently + // with us closing it. If we timed out immediately, we could close the testserver + // before we entered the handler. We're not timing out immediately and there's + // no way we would be done before we entered the handler, but the race detector + // doesn't know this, so synchronize explicitly. + defer func() { donec <- true }() + + c := &Client{Timeout: 500 * time.Millisecond} + + _, err := c.Get(ts.URL) + if err == nil { + t.Fatal("got response from Get; expected error") + } + ue, ok := err.(*url.Error) + if !ok { + t.Fatalf("Got error of type %T; want *url.Error", err) + } + ne, ok := ue.Err.(net.Error) + if !ok { + t.Fatalf("Got url.Error.Err of type %T; want some net.Error", err) + } + if !ne.Timeout() { + t.Error("net.Error.Timeout = false; want true") + } + if got := ne.Error(); !strings.Contains(got, "Client.Timeout exceeded") { + t.Errorf("error string = %q; missing timeout substring", got) + } +} + func TestClientRedirectEatsBody(t *testing.T) { defer afterTest(t) saw := make(chan string, 2) @@ -984,24 +1076,12 @@ func TestClientTrailers(t *testing.T) { r.Trailer.Get("Client-Trailer-B")) } - // TODO: golang.org/issue/7759: there's no way yet for - // the server to set trailers without hijacking, so do - // that for now, just to test the client. Later, in - // Go 1.4, it should be implicit that any mutations - // to w.Header() after the initial write are the - // trailers to be sent, if and only if they were - // previously declared with w.Header().Set("Trailer", - // ..keys..) - w.(Flusher).Flush() - conn, buf, _ := w.(Hijacker).Hijack() - t := Header{} - t.Set("Server-Trailer-A", "valuea") - t.Set("Server-Trailer-C", "valuec") // skipping B - buf.WriteString("0\r\n") // eof - t.Write(buf) - buf.WriteString("\r\n") // end of trailers - buf.Flush() - conn.Close() + // How handlers set Trailers: declare it ahead of time + // with the Trailer header, and then mutate the + // Header() of those values later, after the response + // has been written (we wrote to w above). + w.Header().Set("Server-Trailer-A", "valuea") + w.Header().Set("Server-Trailer-C", "valuec") // skipping B })) defer ts.Close() diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index a0d0fdbbd07..648709dd997 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -14,19 +14,18 @@ import ( "time" ) -// This implementation is done according to RFC 6265: -// -// http://tools.ietf.org/html/rfc6265 - // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an // HTTP response or the Cookie header of an HTTP request. +// +// See http://tools.ietf.org/html/rfc6265 for details. type Cookie struct { - Name string - Value string - Path string - Domain string - Expires time.Time - RawExpires string + Name string + Value string + + Path string // optional + Domain string // optional + Expires time.Time // optional + RawExpires string // for reading cookies only // MaxAge=0 means no 'Max-Age' attribute specified. // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' @@ -126,14 +125,22 @@ func readSetCookies(h Header) []*Cookie { } // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. +// The provided cookie must have a valid Name. Invalid cookies may be +// silently dropped. func SetCookie(w ResponseWriter, cookie *Cookie) { - w.Header().Add("Set-Cookie", cookie.String()) + if v := cookie.String(); v != "" { + w.Header().Add("Set-Cookie", v) + } } // String returns the serialization of the cookie for use in a Cookie // header (if only Name and Value are set) or a Set-Cookie response // header (if other fields are set). +// If c is nil or c.Name is invalid, the empty string is returned. func (c *Cookie) String() string { + if c == nil || !isCookieNameValid(c.Name) { + return "" + } var b bytes.Buffer fmt.Fprintf(&b, "%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) if len(c.Path) > 0 { @@ -156,7 +163,7 @@ func (c *Cookie) String() string { } } if c.Expires.Unix() > 0 { - fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) + fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(TimeFormat)) } if c.MaxAge > 0 { fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) @@ -297,7 +304,7 @@ func sanitizeCookieName(n string) string { // We loosen this as spaces and commas are common in cookie values // but we produce a quoted cookie-value in when value starts or ends // with a comma or space. -// See http://golang.org/issue/7243 for the discussion. +// See https://golang.org/issue/7243 for the discussion. func sanitizeCookieValue(v string) string { v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) if len(v) == 0 { @@ -359,5 +366,8 @@ func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { } func isCookieNameValid(raw string) bool { + if raw == "" { + return false + } return strings.IndexFunc(raw, isNotToken) < 0 } diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index 98dc2fade0d..d474f313476 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -52,6 +52,10 @@ var writeSetCookiesTests = []struct { &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, "cookie-8=eight", }, + { + &Cookie{Name: "cookie-9", Value: "expiring", Expires: time.Unix(1257894000, 0)}, + "cookie-9=expiring; Expires=Tue, 10 Nov 2009 23:00:00 GMT", + }, // The "special" cookies have values containing commas or spaces which // are disallowed by RFC 6265 but are common in the wild. { @@ -90,6 +94,18 @@ var writeSetCookiesTests = []struct { &Cookie{Name: "empty-value", Value: ""}, `empty-value=`, }, + { + nil, + ``, + }, + { + &Cookie{Name: ""}, + ``, + }, + { + &Cookie{Name: "\t"}, + ``, + }, } func TestWriteSetCookies(t *testing.T) { @@ -349,7 +365,7 @@ func TestSetCookieDoubleQuotes(t *testing.T) { {Name: "quoted3", Value: "both"}, } if len(got) != len(want) { - t.Fatal("got %d cookies, want %d", len(got), len(want)) + t.Fatalf("got %d cookies, want %d", len(got), len(want)) } for i, w := range want { g := got[i] diff --git a/libgo/go/net/http/example_test.go b/libgo/go/net/http/example_test.go index 88b97d9e3d7..1774795d379 100644 --- a/libgo/go/net/http/example_test.go +++ b/libgo/go/net/http/example_test.go @@ -6,6 +6,7 @@ package http_test import ( "fmt" + "io" "io/ioutil" "log" "net/http" @@ -86,3 +87,25 @@ func ExampleServeMux_Handle() { fmt.Fprintf(w, "Welcome to the home page!") }) } + +// HTTP Trailers are a set of key/value pairs like headers that come +// after the HTTP response, instead of before. +func ExampleResponseWriter_trailers() { + mux := http.NewServeMux() + mux.HandleFunc("/sendstrailers", func(w http.ResponseWriter, req *http.Request) { + // Before any call to WriteHeader or Write, declare + // the trailers you will set during the HTTP + // response. These three headers are actually sent in + // the trailer. + w.Header().Set("Trailer", "AtEnd1, AtEnd2") + w.Header().Add("Trailer", "AtEnd3") + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header + w.WriteHeader(http.StatusOK) + + w.Header().Set("AtEnd1", "value 1") + io.WriteString(w, "This HTTP response has both headers before this text and trailers at the end.\n") + w.Header().Set("AtEnd2", "value 2") + w.Header().Set("AtEnd3", "value 3") // These will appear as trailers. + }) +} diff --git a/libgo/go/net/http/export_test.go b/libgo/go/net/http/export_test.go index 87b6c0773aa..0457be50da6 100644 --- a/libgo/go/net/http/export_test.go +++ b/libgo/go/net/http/export_test.go @@ -10,9 +10,17 @@ package http import ( "net" "net/url" + "sync" "time" ) +func init() { + // We only want to pay for this cost during testing. + // When not under test, these values are always nil + // and never assigned to. + testHookMu = new(sync.Mutex) +} + func NewLoggingConn(baseName string, c net.Conn) net.Conn { return newLoggingConn(baseName, c) } @@ -78,6 +86,20 @@ func (t *Transport) PutIdleTestConn() bool { }) } +func SetInstallConnClosedHook(f func()) { + testHookPersistConnClosedGotRes = f +} + +func SetEnterRoundTripHook(f func()) { + testHookEnterRoundTrip = f +} + +func SetReadLoopBeforeNextReadHook(f func()) { + testHookMu.Lock() + defer testHookMu.Unlock() + testHookReadLoopBeforeNextRead = f +} + func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { f := func() <-chan time.Time { return ch @@ -106,3 +128,5 @@ func SetPendingDialHooks(before, after func()) { var ExportServerNewConn = (*Server).newConn var ExportCloseWriteAndWait = (*conn).closeWriteAndWait + +var ExportErrRequestCanceled = errRequestCanceled diff --git a/libgo/go/net/http/fcgi/child.go b/libgo/go/net/http/fcgi/child.go index a3beaa33a86..da824ed717e 100644 --- a/libgo/go/net/http/fcgi/child.go +++ b/libgo/go/net/http/fcgi/child.go @@ -144,6 +144,7 @@ func newChild(rwc io.ReadWriteCloser, handler http.Handler) *child { func (c *child) serve() { defer c.conn.Close() + defer c.cleanUp() var rec record for { if err := rec.read(c.conn.rwc); err != nil { @@ -159,6 +160,14 @@ var errCloseConn = errors.New("fcgi: connection should be closed") var emptyBody = ioutil.NopCloser(strings.NewReader("")) +// ErrRequestAborted is returned by Read when a handler attempts to read the +// body of a request that has been aborted by the web server. +var ErrRequestAborted = errors.New("fcgi: request aborted by web server") + +// ErrConnClosed is returned by Read when a handler attempts to read the body of +// a request after the connection to the web server has been closed. +var ErrConnClosed = errors.New("fcgi: connection to web server closed") + func (c *child) handleRecord(rec *record) error { c.mu.Lock() req, ok := c.requests[rec.h.Id] @@ -227,11 +236,13 @@ func (c *child) handleRecord(rec *record) error { // If the filter role is implemented, read the data stream here. return nil case typeAbortRequest: - println("abort") c.mu.Lock() delete(c.requests, rec.h.Id) c.mu.Unlock() c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete) + if req.pw != nil { + req.pw.CloseWithError(ErrRequestAborted) + } if !req.keepConn { // connection will close upon return return errCloseConn @@ -277,6 +288,18 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) { } } +func (c *child) cleanUp() { + c.mu.Lock() + defer c.mu.Unlock() + for _, req := range c.requests { + if req.pw != nil { + // race with call to Close in c.serveRequest doesn't matter because + // Pipe(Reader|Writer).Close are idempotent + req.pw.CloseWithError(ErrConnClosed) + } + } +} + // Serve accepts incoming FastCGI connections on the listener l, creating a new // goroutine for each. The goroutine reads requests and then calls handler // to reply to them. diff --git a/libgo/go/net/http/fcgi/fcgi_test.go b/libgo/go/net/http/fcgi/fcgi_test.go index 6c7e1a9ce83..de0f7f831f6 100644 --- a/libgo/go/net/http/fcgi/fcgi_test.go +++ b/libgo/go/net/http/fcgi/fcgi_test.go @@ -8,6 +8,8 @@ import ( "bytes" "errors" "io" + "io/ioutil" + "net/http" "testing" ) @@ -148,3 +150,107 @@ func TestGetValues(t *testing.T) { t.Errorf(" got: %q\nwant: %q\n", got, want) } } + +func nameValuePair11(nameData, valueData string) []byte { + return bytes.Join( + [][]byte{ + {byte(len(nameData)), byte(len(valueData))}, + []byte(nameData), + []byte(valueData), + }, + nil, + ) +} + +func makeRecord( + recordType recType, + requestId uint16, + contentData []byte, +) []byte { + requestIdB1 := byte(requestId >> 8) + requestIdB0 := byte(requestId) + + contentLength := len(contentData) + contentLengthB1 := byte(contentLength >> 8) + contentLengthB0 := byte(contentLength) + return bytes.Join([][]byte{ + {1, byte(recordType), requestIdB1, requestIdB0, contentLengthB1, + contentLengthB0, 0, 0}, + contentData, + }, + nil) +} + +// a series of FastCGI records that start a request and begin sending the +// request body +var streamBeginTypeStdin = bytes.Join([][]byte{ + // set up request 1 + makeRecord(typeBeginRequest, 1, + []byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}), + // add required parameters to request 1 + makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")), + makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")), + makeRecord(typeParams, 1, nil), + // begin sending body of request 1 + makeRecord(typeStdin, 1, []byte("0123456789abcdef")), +}, + nil) + +var cleanUpTests = []struct { + input []byte + err error +}{ + // confirm that child.handleRecord closes req.pw after aborting req + { + bytes.Join([][]byte{ + streamBeginTypeStdin, + makeRecord(typeAbortRequest, 1, nil), + }, + nil), + ErrRequestAborted, + }, + // confirm that child.serve closes all pipes after error reading record + { + bytes.Join([][]byte{ + streamBeginTypeStdin, + nil, + }, + nil), + ErrConnClosed, + }, +} + +type nopWriteCloser struct { + io.ReadWriter +} + +func (nopWriteCloser) Close() error { + return nil +} + +// Test that child.serve closes the bodies of aborted requests and closes the +// bodies of all requests before returning. Causes deadlock if either condition +// isn't met. See issue 6934. +func TestChildServeCleansUp(t *testing.T) { + for _, tt := range cleanUpTests { + input := make([]byte, len(tt.input)) + copy(input, tt.input) + rc := nopWriteCloser{bytes.NewBuffer(input)} + done := make(chan bool) + c := newChild(rc, http.HandlerFunc(func( + w http.ResponseWriter, + r *http.Request, + ) { + // block on reading body of request + _, err := io.Copy(ioutil.Discard, r.Body) + if err != tt.err { + t.Errorf("Expected %#v, got %#v", tt.err, err) + } + // not reached if body of request isn't closed + done <- true + })) + go c.serve() + // wait for body of request to be closed or all goroutines to block + <-done + } +} diff --git a/libgo/go/net/http/fs.go b/libgo/go/net/http/fs.go index e322f710a5d..75720234c25 100644 --- a/libgo/go/net/http/fs.go +++ b/libgo/go/net/http/fs.go @@ -102,10 +102,10 @@ func dirList(w ResponseWriter, f File) { // The name is otherwise unused; in particular it can be empty and is // never sent in the response. // -// If modtime is not the zero time, ServeContent includes it in a -// Last-Modified header in the response. If the request includes an -// If-Modified-Since header, ServeContent uses modtime to decide -// whether the content needs to be sent at all. +// If modtime is not the zero time or Unix epoch, ServeContent +// includes it in a Last-Modified header in the response. If the +// request includes an If-Modified-Since header, ServeContent uses +// modtime to decide whether the content needs to be sent at all. // // The content's Seek method must work: ServeContent uses // a seek to the end of the content to determine its size. @@ -258,10 +258,15 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, } } +var unixEpochTime = time.Unix(0, 0) + // modtime is the modification time of the resource to be served, or IsZero(). // return value is whether this request is now complete. func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool { - if modtime.IsZero() { + if modtime.IsZero() || modtime.Equal(unixEpochTime) { + // If the file doesn't have a modtime (IsZero), or the modtime + // is obviously garbage (Unix time == 0), then ignore modtimes + // and don't process the If-Modified-Since header. return false } @@ -353,16 +358,16 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec f, err := fs.Open(name) if err != nil { - // TODO expose actual error? - NotFound(w, r) + msg, code := toHTTPError(err) + Error(w, msg, code) return } defer f.Close() d, err1 := f.Stat() if err1 != nil { - // TODO expose actual error? - NotFound(w, r) + msg, code := toHTTPError(err) + Error(w, msg, code) return } @@ -412,6 +417,22 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f) } +// toHTTPError returns a non-specific HTTP error message and status code +// for a given non-nil error value. It's important that toHTTPError does not +// actually return err.Error(), since msg and httpStatus are returned to users, +// and historically Go's ServeContent always returned just "404 Not Found" for +// all errors. We don't want to start leaking information in error messages. +func toHTTPError(err error) (msg string, httpStatus int) { + if os.IsNotExist(err) { + return "404 page not found", StatusNotFound + } + if os.IsPermission(err) { + return "403 Forbidden", StatusForbidden + } + // Default: + return "500 Internal Server Error", StatusInternalServerError +} + // localRedirect gives a Moved Permanently response. // It does not convert relative paths to absolute paths like Redirect does. func localRedirect(w ResponseWriter, r *Request, newPath string) { @@ -422,7 +443,13 @@ func localRedirect(w ResponseWriter, r *Request, newPath string) { w.WriteHeader(StatusMovedPermanently) } -// ServeFile replies to the request with the contents of the named file or directory. +// ServeFile replies to the request with the contents of the named +// file or directory. +// +// As a special case, ServeFile redirects any request where r.URL.Path +// ends in "/index.html" to the same path, without the final +// "index.html". To avoid such redirects either modify the path or +// use ServeContent. func ServeFile(w ResponseWriter, r *Request, name string) { dir, file := filepath.Split(name) serveFile(w, r, Dir(dir), file, false) @@ -439,6 +466,10 @@ type fileHandler struct { // use http.Dir: // // http.Handle("/", http.FileServer(http.Dir("/tmp"))) +// +// As a special case, the returned file server redirects any request +// ending in "/index.html" to the same path, without the final +// "index.html". func FileServer(root FileSystem) Handler { return &fileHandler{root} } @@ -503,7 +534,7 @@ func parseRange(s string, size int64) ([]httpRange, error) { r.length = size - r.start } else { i, err := strconv.ParseInt(start, 10, 64) - if err != nil || i > size || i < 0 { + if err != nil || i >= size || i < 0 { return nil, errors.New("invalid range") } r.start = i diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go index 2ddd4ca5fe9..538f34d7201 100644 --- a/libgo/go/net/http/fs_test.go +++ b/libgo/go/net/http/fs_test.go @@ -50,15 +50,23 @@ var ServeFileRangeTests = []struct { {r: "bytes=2-", code: StatusPartialContent, ranges: []wantRange{{2, testFileLen}}}, {r: "bytes=-5", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 5, testFileLen}}}, {r: "bytes=3-7", code: StatusPartialContent, ranges: []wantRange{{3, 8}}}, - {r: "bytes=20-", code: StatusRequestedRangeNotSatisfiable}, {r: "bytes=0-0,-2", code: StatusPartialContent, ranges: []wantRange{{0, 1}, {testFileLen - 2, testFileLen}}}, {r: "bytes=0-1,5-8", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, 9}}}, {r: "bytes=0-1,5-", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, testFileLen}}}, {r: "bytes=5-1000", code: StatusPartialContent, ranges: []wantRange{{5, testFileLen}}}, {r: "bytes=0-,1-,2-,3-,4-", code: StatusOK}, // ignore wasteful range request - {r: "bytes=0-" + itoa(testFileLen-2), code: StatusPartialContent, ranges: []wantRange{{0, testFileLen - 1}}}, - {r: "bytes=0-" + itoa(testFileLen-1), code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}}, - {r: "bytes=0-" + itoa(testFileLen), code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}}, + {r: "bytes=0-9", code: StatusPartialContent, ranges: []wantRange{{0, testFileLen - 1}}}, + {r: "bytes=0-10", code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}}, + {r: "bytes=0-11", code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}}, + {r: "bytes=10-11", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 1, testFileLen}}}, + {r: "bytes=10-", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 1, testFileLen}}}, + {r: "bytes=11-", code: StatusRequestedRangeNotSatisfiable}, + {r: "bytes=11-12", code: StatusRequestedRangeNotSatisfiable}, + {r: "bytes=12-12", code: StatusRequestedRangeNotSatisfiable}, + {r: "bytes=11-100", code: StatusRequestedRangeNotSatisfiable}, + {r: "bytes=12-100", code: StatusRequestedRangeNotSatisfiable}, + {r: "bytes=100-", code: StatusRequestedRangeNotSatisfiable}, + {r: "bytes=100-1000", code: StatusRequestedRangeNotSatisfiable}, } func TestServeFile(t *testing.T) { @@ -489,6 +497,7 @@ type fakeFileInfo struct { modtime time.Time ents []*fakeFileInfo contents string + err error } func (f *fakeFileInfo) Name() string { return f.basename } @@ -541,6 +550,9 @@ func (fs fakeFS) Open(name string) (File, error) { if !ok { return nil, os.ErrNotExist } + if f.err != nil { + return nil, f.err + } return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil } @@ -743,6 +755,12 @@ func TestServeContent(t *testing.T) { wantContentType: "text/css; charset=utf-8", wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT", }, + "unix_zero_modtime": { + content: strings.NewReader("<html>foo"), + modtime: time.Unix(0, 0), + wantStatus: StatusOK, + wantContentType: "text/html; charset=utf-8", + }, } for testName, tt := range tests { var content io.ReadSeeker @@ -789,6 +807,31 @@ func TestServeContent(t *testing.T) { } } +func TestServeContentErrorMessages(t *testing.T) { + defer afterTest(t) + fs := fakeFS{ + "/500": &fakeFileInfo{ + err: errors.New("random error"), + }, + "/403": &fakeFileInfo{ + err: &os.PathError{Err: os.ErrPermission}, + }, + } + ts := httptest.NewServer(FileServer(fs)) + defer ts.Close() + for _, code := range []int{403, 404, 500} { + res, err := DefaultClient.Get(fmt.Sprintf("%s/%d", ts.URL, code)) + if err != nil { + t.Errorf("Error fetching /%d: %v", code, err) + continue + } + if res.StatusCode != code { + t.Errorf("For /%d, status code = %d; want %d", code, res.StatusCode, code) + } + res.Body.Close() + } +} + // verifies that sendfile is being used on Linux func TestLinuxSendfile(t *testing.T) { defer afterTest(t) diff --git a/libgo/go/net/http/header.go b/libgo/go/net/http/header.go index 153b94370f8..d847b131184 100644 --- a/libgo/go/net/http/header.go +++ b/libgo/go/net/http/header.go @@ -168,6 +168,8 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) 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". +// If s contains a space or invalid header field bytes, it is +// returned without modifications. func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) } // hasToken reports whether token appears with v, ASCII diff --git a/libgo/go/net/http/http_test.go b/libgo/go/net/http/http_test.go new file mode 100644 index 00000000000..dead3b04542 --- /dev/null +++ b/libgo/go/net/http/http_test.go @@ -0,0 +1,58 @@ +// Copyright 2014 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. + +// Tests of internal functions with no better homes. + +package http + +import ( + "reflect" + "testing" +) + +func TestForeachHeaderElement(t *testing.T) { + tests := []struct { + in string + want []string + }{ + {"Foo", []string{"Foo"}}, + {" Foo", []string{"Foo"}}, + {"Foo ", []string{"Foo"}}, + {" Foo ", []string{"Foo"}}, + + {"foo", []string{"foo"}}, + {"anY-cAsE", []string{"anY-cAsE"}}, + + {"", nil}, + {",,,, , ,, ,,, ,", nil}, + + {" Foo,Bar, Baz,lower,,Quux ", []string{"Foo", "Bar", "Baz", "lower", "Quux"}}, + } + for _, tt := range tests { + var got []string + foreachHeaderElement(tt.in, func(v string) { + got = append(got, v) + }) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("foreachHeaderElement(%q) = %q; want %q", tt.in, got, tt.want) + } + } +} + +func TestCleanHost(t *testing.T) { + tests := []struct { + in, want string + }{ + {"www.google.com", "www.google.com"}, + {"www.google.com foo", "www.google.com"}, + {"www.google.com/foo", "www.google.com"}, + {" first character is a space", ""}, + } + for _, tt := range tests { + got := cleanHost(tt.in) + if tt.want != got { + t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) + } + } +} diff --git a/libgo/go/net/http/httptest/server.go b/libgo/go/net/http/httptest/server.go index 789e7bf41e6..96eb0ef6d2f 100644 --- a/libgo/go/net/http/httptest/server.go +++ b/libgo/go/net/http/httptest/server.go @@ -204,25 +204,35 @@ func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end // of ASN.1 time). // generated from src/crypto/tls: -// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var localhostCert = []byte(`-----BEGIN CERTIFICATE----- -MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD -bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj -bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa -IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA -AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud -EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA -AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk -Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 +iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul +rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA +AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 +tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs +h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM +fblo6RBxUQ== -----END CERTIFICATE-----`) // localhostKey is the private key for localhostCert. var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 -0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV -NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d -AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW -MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD -EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA -1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB +l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX +qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo +f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== -----END RSA PRIVATE KEY-----`) diff --git a/libgo/go/net/http/httputil/dump.go b/libgo/go/net/http/httputil/dump.go index ac8f103f9b9..ca2d1cde924 100644 --- a/libgo/go/net/http/httputil/dump.go +++ b/libgo/go/net/http/httputil/dump.go @@ -98,6 +98,14 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { defer pr.Close() defer pw.Close() dr := &delegateReader{c: make(chan io.Reader)} + + t := &http.Transport{ + Dial: func(net, addr string) (net.Conn, error) { + return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil + }, + } + defer t.CloseIdleConnections() + // Wait for the request before replying with a dummy response: go func() { req, err := http.ReadRequest(bufio.NewReader(pr)) @@ -107,16 +115,9 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { io.Copy(ioutil.Discard, req.Body) req.Body.Close() } - dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n") + dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n") }() - t := &http.Transport{ - DisableKeepAlives: true, - Dial: func(net, addr string) (net.Conn, error) { - return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil - }, - } - _, err := t.RoundTrip(reqSend) req.Body = save diff --git a/libgo/go/net/http/httputil/dump_test.go b/libgo/go/net/http/httputil/dump_test.go index 024ee5a86f4..ae67e983ae9 100644 --- a/libgo/go/net/http/httputil/dump_test.go +++ b/libgo/go/net/http/httputil/dump_test.go @@ -71,7 +71,7 @@ var dumpTests = []dumpTest{ WantDumpOut: "GET /foo HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Accept-Encoding: gzip\r\n\r\n", }, @@ -83,7 +83,7 @@ var dumpTests = []dumpTest{ WantDumpOut: "GET /foo HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Accept-Encoding: gzip\r\n\r\n", }, @@ -105,7 +105,7 @@ var dumpTests = []dumpTest{ WantDumpOut: "POST / HTTP/1.1\r\n" + "Host: post.tld\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 6\r\n" + "Accept-Encoding: gzip\r\n\r\n", @@ -130,7 +130,7 @@ var dumpTests = []dumpTest{ WantDumpOut: "POST / HTTP/1.1\r\n" + "Host: post.tld\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 8193\r\n" + "Accept-Encoding: gzip\r\n\r\n" + strings.Repeat("a", 8193), diff --git a/libgo/go/net/http/httputil/reverseproxy.go b/libgo/go/net/http/httputil/reverseproxy.go index ab463701803..3b7a184d933 100644 --- a/libgo/go/net/http/httputil/reverseproxy.go +++ b/libgo/go/net/http/httputil/reverseproxy.go @@ -100,6 +100,24 @@ var hopHeaders = []string{ "Upgrade", } +type requestCanceler interface { + CancelRequest(*http.Request) +} + +type runOnFirstRead struct { + io.Reader + + fn func() // Run before first Read, then set to nil +} + +func (c *runOnFirstRead) Read(bs []byte) (int, error) { + if c.fn != nil { + c.fn() + c.fn = nil + } + return c.Reader.Read(bs) +} + func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { transport := p.Transport if transport == nil { @@ -109,6 +127,34 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { outreq := new(http.Request) *outreq = *req // includes shallow copies of maps, but okay + if closeNotifier, ok := rw.(http.CloseNotifier); ok { + if requestCanceler, ok := transport.(requestCanceler); ok { + reqDone := make(chan struct{}) + defer close(reqDone) + + clientGone := closeNotifier.CloseNotify() + + outreq.Body = struct { + io.Reader + io.Closer + }{ + Reader: &runOnFirstRead{ + Reader: outreq.Body, + fn: func() { + go func() { + select { + case <-clientGone: + requestCanceler.CancelRequest(outreq) + case <-reqDone: + } + }() + }, + }, + Closer: outreq.Body, + } + } + } + p.Director(outreq) outreq.Proto = "HTTP/1.1" outreq.ProtoMajor = 1 @@ -148,7 +194,6 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) return } - defer res.Body.Close() for _, h := range hopHeaders { res.Header.Del(h) @@ -156,8 +201,28 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { copyHeader(rw.Header(), res.Header) + // The "Trailer" header isn't included in the Transport's response, + // at least for *http.Transport. Build it up from Trailer. + if len(res.Trailer) > 0 { + var trailerKeys []string + for k := range res.Trailer { + trailerKeys = append(trailerKeys, k) + } + rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) + } + rw.WriteHeader(res.StatusCode) + if len(res.Trailer) > 0 { + // Force chunking if we saw a response trailer. + // This prevents net/http from calculating the length for short + // bodies and adding a Content-Length. + if fl, ok := rw.(http.Flusher); ok { + fl.Flush() + } + } p.copyResponse(rw, res.Body) + res.Body.Close() // close now, instead of defer, to populate res.Trailer + copyHeader(rw.Header(), res.Trailer) } func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { diff --git a/libgo/go/net/http/httputil/reverseproxy_test.go b/libgo/go/net/http/httputil/reverseproxy_test.go index e9539b44b6e..25947e6a8ab 100644 --- a/libgo/go/net/http/httputil/reverseproxy_test.go +++ b/libgo/go/net/http/httputil/reverseproxy_test.go @@ -8,9 +8,12 @@ package httputil import ( "io/ioutil" + "log" "net/http" "net/http/httptest" "net/url" + "reflect" + "runtime" "strings" "testing" "time" @@ -41,6 +44,7 @@ func TestReverseProxy(t *testing.T) { if g, e := r.Host, "some-name"; g != e { t.Errorf("backend got Host header %q, want %q", g, e) } + w.Header().Set("Trailer", "X-Trailer") w.Header().Set("X-Foo", "bar") w.Header().Set("Upgrade", "foo") w.Header().Set(fakeHopHeader, "foo") @@ -49,6 +53,7 @@ func TestReverseProxy(t *testing.T) { http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"}) w.WriteHeader(backendStatus) w.Write([]byte(backendResponse)) + w.Header().Set("X-Trailer", "trailer_value") })) defer backend.Close() backendURL, err := url.Parse(backend.URL) @@ -83,6 +88,9 @@ func TestReverseProxy(t *testing.T) { if g, e := len(res.Header["Set-Cookie"]), 1; g != e { t.Fatalf("got %d SetCookies, want %d", g, e) } + if g, e := res.Trailer, (http.Header{"X-Trailer": nil}); !reflect.DeepEqual(g, e) { + t.Errorf("before reading body, Trailer = %#v; want %#v", g, e) + } if cookie := res.Cookies()[0]; cookie.Name != "flavor" { t.Errorf("unexpected cookie %q", cookie.Name) } @@ -90,6 +98,10 @@ func TestReverseProxy(t *testing.T) { if g, e := string(bodyBytes), backendResponse; g != e { t.Errorf("got body %q; expected %q", g, e) } + if g, e := res.Trailer.Get("X-Trailer"), "trailer_value"; g != e { + t.Errorf("Trailer(X-Trailer) = %q ; want %q", g, e) + } + } func TestXForwardedFor(t *testing.T) { @@ -211,3 +223,61 @@ func TestReverseProxyFlushInterval(t *testing.T) { t.Error("maxLatencyWriter flushLoop() never exited") } } + +func TestReverseProxyCancellation(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skip("skipping test; see https://golang.org/issue/9554") + } + const backendResponse = "I am the backend" + + reqInFlight := make(chan struct{}) + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + close(reqInFlight) + + select { + case <-time.After(10 * time.Second): + // Note: this should only happen in broken implementations, and the + // closenotify case should be instantaneous. + t.Log("Failed to close backend connection") + t.Fail() + case <-w.(http.CloseNotifier).CloseNotify(): + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte(backendResponse)) + })) + + defer backend.Close() + + backend.Config.ErrorLog = log.New(ioutil.Discard, "", 0) + + backendURL, err := url.Parse(backend.URL) + if err != nil { + t.Fatal(err) + } + + proxyHandler := NewSingleHostReverseProxy(backendURL) + + // Discards errors of the form: + // http: proxy error: read tcp 127.0.0.1:44643: use of closed network connection + proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) + + frontend := httptest.NewServer(proxyHandler) + defer frontend.Close() + + getReq, _ := http.NewRequest("GET", frontend.URL, nil) + go func() { + <-reqInFlight + http.DefaultTransport.(*http.Transport).CancelRequest(getReq) + }() + res, err := http.DefaultClient.Do(getReq) + if res != nil { + t.Fatal("Non-nil response") + } + if err == nil { + // This should be an error like: + // Get http://127.0.0.1:58079: read tcp 127.0.0.1:58079: + // use of closed network connection + t.Fatal("DefaultClient.Do() returned nil error") + } +} diff --git a/libgo/go/net/http/internal/chunked.go b/libgo/go/net/http/internal/chunked.go index 9294deb3e5e..6d7c69874d9 100644 --- a/libgo/go/net/http/internal/chunked.go +++ b/libgo/go/net/http/internal/chunked.go @@ -173,8 +173,12 @@ func (cw *chunkedWriter) Write(data []byte) (n int, err error) { err = io.ErrShortWrite return } - _, err = io.WriteString(cw.Wire, "\r\n") - + if _, err = io.WriteString(cw.Wire, "\r\n"); err != nil { + return + } + if bw, ok := cw.Wire.(*FlushAfterChunkWriter); ok { + err = bw.Flush() + } return } @@ -183,6 +187,15 @@ func (cw *chunkedWriter) Close() error { return err } +// FlushAfterChunkWriter signals from the caller of NewChunkedWriter +// that each chunk should be followed by a flush. It is used by the +// http.Transport code to keep the buffering behavior for headers and +// trailers, but flush out chunks aggressively in the middle for +// request bodies which may be generated slowly. See Issue 6574. +type FlushAfterChunkWriter struct { + *bufio.Writer +} + func parseHexUint(v []byte) (n uint64, err error) { for _, b := range v { n <<= 4 diff --git a/libgo/go/net/http/lex.go b/libgo/go/net/http/lex.go index cb33318f49b..50b14f8b325 100644 --- a/libgo/go/net/http/lex.go +++ b/libgo/go/net/http/lex.go @@ -4,6 +4,11 @@ package http +import ( + "strings" + "unicode/utf8" +) + // This file deals with lexical matters of HTTP var isTokenTable = [127]bool{ @@ -94,3 +99,71 @@ func isToken(r rune) bool { func isNotToken(r rune) bool { return !isToken(r) } + +// headerValuesContainsToken reports whether any string in values +// contains the provided token, ASCII case-insensitively. +func headerValuesContainsToken(values []string, token string) bool { + for _, v := range values { + if headerValueContainsToken(v, token) { + return true + } + } + return false +} + +// isOWS reports whether b is an optional whitespace byte, as defined +// by RFC 7230 section 3.2.3. +func isOWS(b byte) bool { return b == ' ' || b == '\t' } + +// trimOWS returns x with all optional whitespace removes from the +// beginning and end. +func trimOWS(x string) string { + // TODO: consider using strings.Trim(x, " \t") instead, + // if and when it's fast enough. See issue 10292. + // But this ASCII-only code will probably always beat UTF-8 + // aware code. + for len(x) > 0 && isOWS(x[0]) { + x = x[1:] + } + for len(x) > 0 && isOWS(x[len(x)-1]) { + x = x[:len(x)-1] + } + return x +} + +// headerValueContainsToken reports whether v (assumed to be a +// 0#element, in the ABNF extension described in RFC 7230 section 7) +// contains token amongst its comma-separated tokens, ASCII +// case-insensitively. +func headerValueContainsToken(v string, token string) bool { + v = trimOWS(v) + if comma := strings.IndexByte(v, ','); comma != -1 { + return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token) + } + return tokenEqual(v, token) +} + +// lowerASCII returns the ASCII lowercase version of b. +func lowerASCII(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// tokenEqual reports whether t1 and t2 are equal, ASCII case-insensitively. +func tokenEqual(t1, t2 string) bool { + if len(t1) != len(t2) { + return false + } + for i, b := range t1 { + if b >= utf8.RuneSelf { + // No UTF-8 or non-ASCII allowed in tokens. + return false + } + if lowerASCII(byte(b)) != lowerASCII(t2[i]) { + return false + } + } + return true +} diff --git a/libgo/go/net/http/lex_test.go b/libgo/go/net/http/lex_test.go index 6d9d294f703..986fda17dcd 100644 --- a/libgo/go/net/http/lex_test.go +++ b/libgo/go/net/http/lex_test.go @@ -29,3 +29,73 @@ func TestIsToken(t *testing.T) { } } } + +func TestHeaderValuesContainsToken(t *testing.T) { + tests := []struct { + vals []string + token string + want bool + }{ + { + vals: []string{"foo"}, + token: "foo", + want: true, + }, + { + vals: []string{"bar", "foo"}, + token: "foo", + want: true, + }, + { + vals: []string{"foo"}, + token: "FOO", + want: true, + }, + { + vals: []string{"foo"}, + token: "bar", + want: false, + }, + { + vals: []string{" foo "}, + token: "FOO", + want: true, + }, + { + vals: []string{"foo,bar"}, + token: "FOO", + want: true, + }, + { + vals: []string{"bar,foo,bar"}, + token: "FOO", + want: true, + }, + { + vals: []string{"bar , foo"}, + token: "FOO", + want: true, + }, + { + vals: []string{"foo ,bar "}, + token: "FOO", + want: true, + }, + { + vals: []string{"bar, foo ,bar"}, + token: "FOO", + want: true, + }, + { + vals: []string{"bar , foo"}, + token: "FOO", + want: true, + }, + } + for _, tt := range tests { + got := headerValuesContainsToken(tt.vals, tt.token) + if got != tt.want { + t.Errorf("headerValuesContainsToken(%q, %q) = %v; want %v", tt.vals, tt.token, got, tt.want) + } + } +} diff --git a/libgo/go/net/http/main_test.go b/libgo/go/net/http/main_test.go index b8c71fd19fd..12eea6f0e11 100644 --- a/libgo/go/net/http/main_test.go +++ b/libgo/go/net/http/main_test.go @@ -56,17 +56,21 @@ func goroutineLeaked() bool { // not counting goroutines for leakage in -short mode return false } - gs := interestingGoroutines() - n := 0 - stackCount := make(map[string]int) - for _, g := range gs { - stackCount[g]++ - n++ - } - - if n == 0 { - return false + var stackCount map[string]int + for i := 0; i < 5; i++ { + n := 0 + stackCount = make(map[string]int) + gs := interestingGoroutines() + for _, g := range gs { + stackCount[g]++ + n++ + } + if n == 0 { + return false + } + // Wait for goroutines to schedule and die off: + time.Sleep(100 * time.Millisecond) } fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n") for stack, count := range stackCount { @@ -75,7 +79,7 @@ func goroutineLeaked() bool { return true } -func afterTest(t *testing.T) { +func afterTest(t testing.TB) { http.DefaultTransport.(*http.Transport).CloseIdleConnections() if testing.Short() { return diff --git a/libgo/go/net/http/npn_test.go b/libgo/go/net/http/npn_test.go index 98b8930d064..e2e911d3dd1 100644 --- a/libgo/go/net/http/npn_test.go +++ b/libgo/go/net/http/npn_test.go @@ -6,6 +6,7 @@ package http_test import ( "bufio" + "bytes" "crypto/tls" "fmt" "io" @@ -17,6 +18,7 @@ import ( ) func TestNextProtoUpgrade(t *testing.T) { + defer afterTest(t) ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { fmt.Fprintf(w, "path=%s,proto=", r.URL.Path) if r.TLS != nil { @@ -38,12 +40,12 @@ func TestNextProtoUpgrade(t *testing.T) { ts.StartTLS() defer ts.Close() - tr := newTLSTransport(t, ts) - defer tr.CloseIdleConnections() - c := &Client{Transport: tr} - // Normal request, without NPN. { + tr := newTLSTransport(t, ts) + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) @@ -60,11 +62,17 @@ func TestNextProtoUpgrade(t *testing.T) { // Request to an advertised but unhandled NPN protocol. // Server will hang up. { - tr.CloseIdleConnections() + tr := newTLSTransport(t, ts) tr.TLSClientConfig.NextProtos = []string{"unhandled-proto"} - _, err := c.Get(ts.URL) + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + + res, err := c.Get(ts.URL) if err == nil { - t.Errorf("expected error on unhandled-proto request") + defer res.Body.Close() + var buf bytes.Buffer + res.Write(&buf) + t.Errorf("expected error on unhandled-proto request; got: %s", buf.Bytes()) } } diff --git a/libgo/go/net/http/pprof/pprof.go b/libgo/go/net/http/pprof/pprof.go index a23f1bc4bc6..8994392b1e4 100644 --- a/libgo/go/net/http/pprof/pprof.go +++ b/libgo/go/net/http/pprof/pprof.go @@ -34,12 +34,16 @@ // // go tool pprof http://localhost:6060/debug/pprof/block // +// Or to collect a 5-second execution trace: +// +// wget http://localhost:6060/debug/pprof/trace?seconds=5 +// // To view all available profiles, open http://localhost:6060/debug/pprof/ // in your browser. // // For a study of the facility in action, visit // -// http://blog.golang.org/2011/06/profiling-go-programs.html +// https://blog.golang.org/2011/06/profiling-go-programs.html // package pprof @@ -64,6 +68,7 @@ func init() { http.Handle("/debug/pprof/cmdline", http.HandlerFunc(Cmdline)) http.Handle("/debug/pprof/profile", http.HandlerFunc(Profile)) http.Handle("/debug/pprof/symbol", http.HandlerFunc(Symbol)) + http.Handle("/debug/pprof/trace", http.HandlerFunc(Trace)) } // Cmdline responds with the running program's @@ -98,6 +103,33 @@ func Profile(w http.ResponseWriter, r *http.Request) { pprof.StopCPUProfile() } +// Trace responds with the execution trace in binary form. +// Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified. +// The package initialization registers it as /debug/pprof/trace. +func Trace(w http.ResponseWriter, r *http.Request) { + sec, _ := strconv.ParseInt(r.FormValue("seconds"), 10, 64) + if sec == 0 { + sec = 1 + } + + // Set Content Type assuming trace.Start will work, + // because if it does it starts writing. + w.Header().Set("Content-Type", "application/octet-stream") + w.Write([]byte("tracing not yet supported with gccgo")) + /* + if err := trace.Start(w); err != nil { + // trace.Start failed, so no writes yet. + // Can change header back to text content and send error code. + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Could not enable tracing: %s\n", err) + return + } + time.Sleep(time.Duration(sec) * time.Second) + trace.Stop() + */ +} + // Symbol looks up the program counters listed in the request, // responding with a table mapping program counters to function names. // The package initialization registers it as /debug/pprof/symbol. @@ -193,17 +225,17 @@ var indexTmpl = template.Must(template.New("index").Parse(`<html> <head> <title>/debug/pprof/</title> </head> +<body> /debug/pprof/<br> <br> -<body> profiles:<br> <table> {{range .}} -<tr><td align=right>{{.Count}}<td><a href="/debug/pprof/{{.Name}}?debug=1">{{.Name}}</a> +<tr><td align=right>{{.Count}}<td><a href="{{.Name}}?debug=1">{{.Name}}</a> {{end}} </table> <br> -<a href="/debug/pprof/goroutine?debug=2">full goroutine stack dump</a><br> +<a href="goroutine?debug=2">full goroutine stack dump</a><br> </body> </html> `)) diff --git a/libgo/go/net/http/proxy_test.go b/libgo/go/net/http/proxy_test.go index b6aed3792b6..823d1447ee9 100644 --- a/libgo/go/net/http/proxy_test.go +++ b/libgo/go/net/http/proxy_test.go @@ -18,7 +18,7 @@ var UseProxyTests = []struct { match bool }{ // Never proxy localhost: - {"localhost:80", false}, + {"localhost", false}, {"127.0.0.1", false}, {"127.0.0.2", false}, {"[::1]", false}, diff --git a/libgo/go/net/http/readrequest_test.go b/libgo/go/net/http/readrequest_test.go index e930d99af62..60e2be41d17 100644 --- a/libgo/go/net/http/readrequest_test.go +++ b/libgo/go/net/http/readrequest_test.go @@ -9,6 +9,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net/url" "reflect" "strings" @@ -177,6 +178,36 @@ var reqTests = []reqTest{ noError, }, + // Tests chunked body and a bogus Content-Length which should be deleted. + { + "POST / HTTP/1.1\r\n" + + "Host: foo.com\r\n" + + "Transfer-Encoding: chunked\r\n" + + "Content-Length: 9999\r\n\r\n" + // to be removed. + "3\r\nfoo\r\n" + + "3\r\nbar\r\n" + + "0\r\n" + + "\r\n", + &Request{ + Method: "POST", + URL: &url.URL{ + Path: "/", + }, + TransferEncoding: []string{"chunked"}, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: Header{}, + ContentLength: -1, + Host: "foo.com", + RequestURI: "/", + }, + + "foobar", + noTrailer, + noError, + }, + // CONNECT request with domain name: { "CONNECT www.google.com:443 HTTP/1.1\r\n\r\n", @@ -323,6 +354,32 @@ var reqTests = []reqTest{ noTrailer, noError, }, + + // HEAD with Content-Length 0. Make sure this is permitted, + // since I think we used to send it. + { + "HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", + &Request{ + Method: "HEAD", + URL: &url.URL{ + Path: "/", + }, + Header: Header{ + "Connection": []string{"close"}, + "Content-Length": []string{"0"}, + }, + Host: "issue8261.com", + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Close: true, + RequestURI: "/", + }, + + noBody, + noTrailer, + noError, + }, } func TestReadRequest(t *testing.T) { @@ -356,3 +413,34 @@ func TestReadRequest(t *testing.T) { } } } + +// reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters, +// ending in \r\n\r\n +func reqBytes(req string) []byte { + return []byte(strings.Replace(strings.TrimSpace(req), "\n", "\r\n", -1) + "\r\n\r\n") +} + +var badRequestTests = []struct { + name string + req []byte +}{ + {"bad_connect_host", reqBytes("CONNECT []%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a HTTP/1.0")}, + {"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1 +Content-Length: 3 +Content-Length: 4 + +abc`)}, + {"smuggle_content_len_head", reqBytes(`HEAD / HTTP/1.1 +Host: foo +Content-Length: 5`)}, +} + +func TestReadRequest_Bad(t *testing.T) { + for _, tt := range badRequestTests { + got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req))) + if err == nil { + all, err := ioutil.ReadAll(got.Body) + t.Errorf("%s: got unexpected request = %#v\n Body = %q, %v", tt.name, got, all, err) + } + } +} diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index 487eebcb841..31fe45a4edb 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -25,9 +25,6 @@ import ( ) const ( - maxValueLength = 4096 - maxHeaderLines = 1024 - chunkSize = 4 << 10 // 4 KB chunks defaultMaxMemory = 32 << 20 // 32 MB ) @@ -172,8 +169,9 @@ type Request struct { // The HTTP client ignores Form and uses Body instead. Form url.Values - // PostForm contains the parsed form data from POST or PUT - // body parameters. + // PostForm contains the parsed form data from POST, PATCH, + // or PUT body parameters. + // // This field is only available after ParseForm is called. // The HTTP client ignores PostForm and uses Body instead. PostForm url.Values @@ -226,6 +224,13 @@ type Request struct { // otherwise it leaves the field nil. // This field is ignored by the HTTP client. TLS *tls.ConnectionState + + // Cancel is an optional channel whose closure indicates that the client + // request should be regarded as canceled. Not all implementations of + // RoundTripper may support Cancel. + // + // For server requests, this field is not applicable. + Cancel <-chan struct{} } // ProtoAtLeast reports whether the HTTP protocol used @@ -245,6 +250,7 @@ func (r *Request) Cookies() []*Cookie { return readCookies(r.Header, "") } +// ErrNoCookie is returned by Request's Cookie method when a cookie is not found. var ErrNoCookie = errors.New("http: named cookie not present") // Cookie returns the named cookie provided in the request or @@ -329,13 +335,12 @@ func valueOrDefault(value, def string) string { } // NOTE: This is not intended to reflect the actual Go version being used. -// It was changed from "Go http package" to "Go 1.1 package http" at the -// time of the Go 1.1 release because the former User-Agent had ended up -// on a blacklist for some intrusion detection systems. +// It was changed at the time of Go 1.1 release because the former User-Agent +// had ended up on a blacklist for some intrusion detection systems. // See https://codereview.appspot.com/7532043. -const defaultUserAgent = "Go 1.1 package http" +const defaultUserAgent = "Go-http-client/1.1" -// Write writes an HTTP/1.1 request -- header and body -- in wire format. +// Write writes an HTTP/1.1 request, which is the header and body, in wire format. // This method consults the following fields of the request: // Host // URL @@ -364,14 +369,23 @@ func (r *Request) WriteProxy(w io.Writer) error { // extraHeaders may be nil func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error { - host := req.Host + // Find the target host. Prefer the Host: header, but if that + // is not given, use the host from the request URL. + // + // Clean the host, in case it arrives with unexpected stuff in it. + host := cleanHost(req.Host) if host == "" { if req.URL == nil { return errors.New("http: Request.Write on Request with no Host or URL set") } - host = req.URL.Host + host = cleanHost(req.URL.Host) } + // According to RFC 6874, an HTTP client, proxy, or other + // intermediary must remove any IPv6 zone identifier attached + // to an outgoing URI. + host = removeZone(host) + ruri := req.URL.RequestURI() if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { ruri = req.URL.Scheme + "://" + host + ruri @@ -456,6 +470,39 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err return nil } +// cleanHost strips anything after '/' or ' '. +// Ideally we'd clean the Host header according to the spec: +// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") +// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) +// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) +// But practically, what we are trying to avoid is the situation in +// issue 11206, where a malformed Host header used in the proxy context +// would create a bad request. So it is enough to just truncate at the +// first offending character. +func cleanHost(in string) string { + if i := strings.IndexAny(in, " /"); i != -1 { + return in[:i] + } + return in +} + +// removeZone removes IPv6 zone identifer from host. +// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" +func removeZone(host string) string { + if !strings.HasPrefix(host, "[") { + return host + } + i := strings.LastIndex(host, "]") + if i < 0 { + return host + } + j := strings.LastIndex(host[:i], "%") + if j < 0 { + return host + } + return host[:j] + host[i:] +} + // ParseHTTPVersion parses a HTTP version string. // "HTTP/1.0" returns (1, 0, true). func ParseHTTPVersion(vers string) (major, minor int, ok bool) { @@ -489,6 +536,13 @@ func ParseHTTPVersion(vers string) (major, minor int, ok bool) { // If the provided body is also an io.Closer, the returned // Request.Body is set to body and will be closed by the Client // methods Do, Post, and PostForm, and Transport.RoundTrip. +// +// NewRequest returns a Request suitable for use with Client.Do or +// Transport.RoundTrip. +// To create a request for use with testing a Server Handler use either +// ReadRequest or manually update the Request fields. See the Request +// type's documentation for the difference between inbound and outbound +// request fields. func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { u, err := url.Parse(urlStr) if err != nil { @@ -536,10 +590,11 @@ func (r *Request) BasicAuth() (username, password string, ok bool) { // parseBasicAuth parses an HTTP Basic Authentication string. // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). func parseBasicAuth(auth string) (username, password string, ok bool) { - if !strings.HasPrefix(auth, "Basic ") { + const prefix = "Basic " + if !strings.HasPrefix(auth, prefix) { return } - c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) if err != nil { return } @@ -587,7 +642,7 @@ func putTextprotoReader(r *textproto.Reader) { textprotoReaderPool.Put(r) } -// ReadRequest reads and parses a request from b. +// ReadRequest reads and parses an incoming request from b. func ReadRequest(b *bufio.Reader) (req *Request, err error) { tp := newTextprotoReader(b) @@ -660,19 +715,20 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) { fixPragmaCacheControl(req.Header) + req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false) + err = readTransfer(req, b) if err != nil { return nil, err } - req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false) return req, nil } // MaxBytesReader is similar to io.LimitReader but is intended for // limiting the size of incoming request bodies. In contrast to // io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a -// non-EOF error for a Read beyond the limit, and Closes the +// non-EOF error for a Read beyond the limit, and closes the // underlying reader when its Close method is called. // // MaxBytesReader prevents clients from accidentally or maliciously @@ -686,23 +742,52 @@ type maxBytesReader struct { r io.ReadCloser // underlying reader n int64 // max bytes remaining stopped bool + sawEOF bool +} + +func (l *maxBytesReader) tooLarge() (n int, err error) { + if !l.stopped { + l.stopped = true + if res, ok := l.w.(*response); ok { + res.requestTooLarge() + } + } + return 0, errors.New("http: request body too large") } func (l *maxBytesReader) Read(p []byte) (n int, err error) { - if l.n <= 0 { - if !l.stopped { - l.stopped = true - if res, ok := l.w.(*response); ok { - res.requestTooLarge() - } + toRead := l.n + if l.n == 0 { + if l.sawEOF { + return l.tooLarge() } - return 0, errors.New("http: request body too large") + // The underlying io.Reader may not return (0, io.EOF) + // at EOF if the requested size is 0, so read 1 byte + // instead. The io.Reader docs are a bit ambiguous + // about the return value of Read when 0 bytes are + // requested, and {bytes,strings}.Reader gets it wrong + // too (it returns (0, nil) even at EOF). + toRead = 1 } - if int64(len(p)) > l.n { - p = p[:l.n] + if int64(len(p)) > toRead { + p = p[:toRead] } n, err = l.r.Read(p) + if err == io.EOF { + l.sawEOF = true + } + if l.n == 0 { + // If we had zero bytes to read remaining (but hadn't seen EOF) + // and we get a byte here, that means we went over our limit. + if n > 0 { + return l.tooLarge() + } + return 0, err + } l.n -= int64(n) + if l.n < 0 { + l.n = 0 + } return } @@ -852,6 +937,7 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error { // POST and PUT body parameters take precedence over URL query string values. // FormValue calls ParseMultipartForm and ParseForm if necessary and ignores // any errors returned by these functions. +// If key is not present, FormValue returns the empty string. // To access multiple values of the same key, call ParseForm and // then inspect Request.Form directly. func (r *Request) FormValue(key string) string { @@ -868,6 +954,7 @@ func (r *Request) FormValue(key string) string { // or PUT request body. URL query parameters are ignored. // PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores // any errors returned by these functions. +// If key is not present, PostFormValue returns the empty string. func (r *Request) PostFormValue(key string) string { if r.PostForm == nil { r.ParseMultipartForm(defaultMaxMemory) diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index 759ea4e8b5d..627620c0c41 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -178,6 +178,7 @@ func TestParseMultipartForm(t *testing.T) { } func TestRedirect(t *testing.T) { + defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { switch r.URL.Path { case "/": @@ -326,13 +327,31 @@ func TestReadRequestErrors(t *testing.T) { } } +var newRequestHostTests = []struct { + in, out string +}{ + {"http://www.example.com/", "www.example.com"}, + {"http://www.example.com:8080/", "www.example.com:8080"}, + + {"http://192.168.0.1/", "192.168.0.1"}, + {"http://192.168.0.1:8080/", "192.168.0.1:8080"}, + + {"http://[fe80::1]/", "[fe80::1]"}, + {"http://[fe80::1]:8080/", "[fe80::1]:8080"}, + {"http://[fe80::1%25en0]/", "[fe80::1%en0]"}, + {"http://[fe80::1%25en0]:8080/", "[fe80::1%en0]:8080"}, +} + func TestNewRequestHost(t *testing.T) { - req, err := NewRequest("GET", "http://localhost:1234/", nil) - if err != nil { - t.Fatal(err) - } - if req.Host != "localhost:1234" { - t.Errorf("Host = %q; want localhost:1234", req.Host) + for i, tt := range newRequestHostTests { + req, err := NewRequest("GET", tt.in, nil) + if err != nil { + t.Errorf("#%v: %v", i, err) + continue + } + if req.Host != tt.out { + t.Errorf("got %q; want %q", req.Host, tt.out) + } } } @@ -402,8 +421,6 @@ type getBasicAuthTest struct { ok bool } -type parseBasicAuthTest getBasicAuthTest - type basicAuthCredentialsTest struct { username, password string } @@ -496,6 +513,82 @@ func TestRequestWriteBufferedWriter(t *testing.T) { } } +func TestRequestBadHost(t *testing.T) { + got := []string{} + req, err := NewRequest("GET", "http://foo.com with spaces/after", nil) + if err != nil { + t.Fatal(err) + } + req.Write(logWrites{t, &got}) + want := []string{ + "GET /after HTTP/1.1\r\n", + "Host: foo.com\r\n", + "User-Agent: " + DefaultUserAgent + "\r\n", + "\r\n", + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Writes = %q\n Want = %q", got, want) + } +} + +func TestStarRequest(t *testing.T) { + req, err := ReadRequest(bufio.NewReader(strings.NewReader("M-SEARCH * HTTP/1.1\r\n\r\n"))) + if err != nil { + return + } + var out bytes.Buffer + if err := req.Write(&out); err != nil { + t.Fatal(err) + } + back, err := ReadRequest(bufio.NewReader(&out)) + if err != nil { + t.Fatal(err) + } + // Ignore the Headers (the User-Agent breaks the deep equal, + // but we don't care about it) + req.Header = nil + back.Header = nil + if !reflect.DeepEqual(req, back) { + t.Errorf("Original request doesn't match Request read back.") + t.Logf("Original: %#v", req) + t.Logf("Original.URL: %#v", req.URL) + t.Logf("Wrote: %s", out.Bytes()) + t.Logf("Read back (doesn't match Original): %#v", back) + } +} + +type responseWriterJustWriter struct { + io.Writer +} + +func (responseWriterJustWriter) Header() Header { panic("should not be called") } +func (responseWriterJustWriter) WriteHeader(int) { panic("should not be called") } + +// delayedEOFReader never returns (n > 0, io.EOF), instead putting +// off the io.EOF until a subsequent Read call. +type delayedEOFReader struct { + r io.Reader +} + +func (dr delayedEOFReader) Read(p []byte) (n int, err error) { + n, err = dr.r.Read(p) + if n > 0 && err == io.EOF { + err = nil + } + return +} + +func TestIssue10884_MaxBytesEOF(t *testing.T) { + dst := ioutil.Discard + _, err := io.Copy(dst, MaxBytesReader( + responseWriterJustWriter{dst}, + ioutil.NopCloser(delayedEOFReader{strings.NewReader("12345")}), + 5)) + if err != nil { + t.Fatal(err) + } +} + func testMissingFile(t *testing.T, req *Request) { f, fh, err := req.FormFile("missing") if f != nil { diff --git a/libgo/go/net/http/requestwrite_test.go b/libgo/go/net/http/requestwrite_test.go index 7a6bd587863..cfb95b0a800 100644 --- a/libgo/go/net/http/requestwrite_test.go +++ b/libgo/go/net/http/requestwrite_test.go @@ -93,13 +93,13 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), }, @@ -123,14 +123,14 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "POST /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Connection: close\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Connection: close\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), @@ -156,7 +156,7 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "POST /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Connection: close\r\n" + "Content-Length: 6\r\n" + "\r\n" + @@ -164,7 +164,7 @@ var reqWriteTests = []reqWriteTest{ WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Connection: close\r\n" + "Content-Length: 6\r\n" + "\r\n" + @@ -187,14 +187,14 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 6\r\n" + "\r\n" + "abcdef", WantProxy: "POST http://example.com/ HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 6\r\n" + "\r\n" + "abcdef", @@ -210,7 +210,7 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "\r\n", }, @@ -232,13 +232,13 @@ var reqWriteTests = []reqWriteTest{ // Also, nginx expects it for POST and PUT. WantWrite: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 0\r\n" + "\r\n", WantProxy: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 0\r\n" + "\r\n", }, @@ -258,13 +258,13 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("x") + chunk(""), WantProxy: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("x") + chunk(""), }, @@ -365,7 +365,7 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET /foo HTTP/1.1\r\n" + "Host: \r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "X-Foo: X-Bar\r\n\r\n", }, @@ -391,7 +391,7 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET /search HTTP/1.1\r\n" + "Host: \r\n" + - "User-Agent: Go 1.1 package http\r\n\r\n", + "User-Agent: Go-http-client/1.1\r\n\r\n", }, // Opaque test #1 from golang.org/issue/4860 @@ -410,7 +410,7 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n\r\n", + "User-Agent: Go-http-client/1.1\r\n\r\n", }, // Opaque test #2 from golang.org/issue/4860 @@ -429,7 +429,7 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" + "Host: x.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n\r\n", + "User-Agent: Go-http-client/1.1\r\n\r\n", }, // Testing custom case in header keys. Issue 5022. @@ -451,10 +451,41 @@ var reqWriteTests = []reqWriteTest{ WantWrite: "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "ALL-CAPS: x\r\n" + "\r\n", }, + + // Request with host header field; IPv6 address with zone identifier + { + Req: Request{ + Method: "GET", + URL: &url.URL{ + Host: "[fe80::1%en0]", + }, + }, + + WantWrite: "GET / HTTP/1.1\r\n" + + "Host: [fe80::1]\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + + "\r\n", + }, + + // Request with optional host header field; IPv6 address with zone identifier + { + Req: Request{ + Method: "GET", + URL: &url.URL{ + Host: "www.example.com", + }, + Host: "[fe80::1%en0]:8080", + }, + + WantWrite: "GET / HTTP/1.1\r\n" + + "Host: [fe80::1]:8080\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + + "\r\n", + }, } func TestRequestWrite(t *testing.T) { @@ -538,7 +569,7 @@ func TestRequestWriteClosesBody(t *testing.T) { } expected := "POST / HTTP/1.1\r\n" + "Host: foo.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + // TODO: currently we don't buffer before chunking, so we get a // single "m" chunk before the other chunks, as this was the 1-byte diff --git a/libgo/go/net/http/response.go b/libgo/go/net/http/response.go index 5d2c39080e4..76b85385244 100644 --- a/libgo/go/net/http/response.go +++ b/libgo/go/net/http/response.go @@ -48,7 +48,10 @@ type Response struct { // The http Client and Transport guarantee that Body is always // non-nil, even on responses without a body or responses with // a zero-length body. It is the caller's responsibility to - // close Body. + // close Body. The default HTTP client's Transport does not + // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections + // ("keep-alive") unless the Body is read to completion and is + // closed. // // The Body is automatically dechunked if the server replied // with a "chunked" Transfer-Encoding. @@ -90,6 +93,8 @@ func (r *Response) Cookies() []*Cookie { return readSetCookies(r.Header) } +// ErrNoLocation is returned by Response's Location method +// when no Location header is present. var ErrNoLocation = errors.New("http: no Location header in response") // Location returns the URL of the response's "Location" header, @@ -186,8 +191,10 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { r.ProtoMajor == major && r.ProtoMinor >= minor } -// Writes the response (header, body and trailer) in wire format. This method -// consults the following fields of the response: +// Write writes r to w in the HTTP/1.n server response format, +// including the status line, headers, body, and optional trailer. +// +// This method consults the following fields of the response r: // // StatusCode // ProtoMajor @@ -199,7 +206,7 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { // ContentLength // Header, values for non-canonical keys will have unpredictable behavior // -// Body is closed after it is sent. +// The Response Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { // Status line text := r.Status diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go index 06e940d9aba..421cf55f491 100644 --- a/libgo/go/net/http/response_test.go +++ b/libgo/go/net/http/response_test.go @@ -405,6 +405,57 @@ some body`, "foobar", }, + + // Both keep-alive and close, on the same Connection line. (Issue 8840) + { + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 256\r\n" + + "Connection: keep-alive, close\r\n" + + "\r\n", + + Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq("HEAD"), + Header: Header{ + "Content-Length": {"256"}, + }, + TransferEncoding: nil, + Close: true, + ContentLength: 256, + }, + + "", + }, + + // Both keep-alive and close, on different Connection lines. (Issue 8840) + { + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 256\r\n" + + "Connection: keep-alive\r\n" + + "Connection: close\r\n" + + "\r\n", + + Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq("HEAD"), + Header: Header{ + "Content-Length": {"256"}, + }, + TransferEncoding: nil, + Close: true, + ContentLength: 256, + }, + + "", + }, } func TestReadResponse(t *testing.T) { diff --git a/libgo/go/net/http/responsewrite_test.go b/libgo/go/net/http/responsewrite_test.go index 585b13b8504..5b8d47ab581 100644 --- a/libgo/go/net/http/responsewrite_test.go +++ b/libgo/go/net/http/responsewrite_test.go @@ -207,6 +207,21 @@ func TestResponseWrite(t *testing.T) { }, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", }, + + // When a response to a POST has Content-Length: -1, make sure we don't + // write the Content-Length as -1. + { + Response{ + StatusCode: StatusOK, + ProtoMajor: 1, + ProtoMinor: 1, + Request: &Request{Method: "POST"}, + Header: Header{}, + ContentLength: -1, + Body: ioutil.NopCloser(strings.NewReader("abcdef")), + }, + "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nabcdef", + }, } for i := range respWriteTests { diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index 6bd168d3de3..d51417eb4a0 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -20,6 +20,7 @@ import ( . "net/http" "net/http/httptest" "net/http/httputil" + "net/http/internal" "net/url" "os" "os/exec" @@ -146,6 +147,7 @@ func (ht handlerTest) rawResponse(req string) string { } func TestConsumingBodyOnNextConn(t *testing.T) { + defer afterTest(t) conn := new(testConn) for i := 0; i < 2; i++ { conn.readBuf.Write([]byte( @@ -205,6 +207,7 @@ var handlers = []struct { }{ {"/", "Default"}, {"/someDir/", "someDir"}, + {"/#/", "hash"}, {"someHost.com/someDir/", "someHost.com/someDir"}, } @@ -213,12 +216,14 @@ var vtests = []struct { expected string }{ {"http://localhost/someDir/apage", "someDir"}, + {"http://localhost/%23/apage", "hash"}, {"http://localhost/otherDir/apage", "Default"}, {"http://someHost.com/someDir/apage", "someHost.com/someDir"}, {"http://otherHost.com/someDir/apage", "someDir"}, {"http://otherHost.com/aDir/apage", "Default"}, // redirections for trees {"http://localhost/someDir", "/someDir/"}, + {"http://localhost/%23", "/%23/"}, {"http://someHost.com/someDir", "/someDir/"}, } @@ -416,7 +421,7 @@ func TestServeMuxHandlerRedirects(t *testing.T) { } } -// Tests for http://code.google.com/p/go/issues/detail?id=900 +// Tests for https://golang.org/issue/900 func TestMuxRedirectLeadingSlashes(t *testing.T) { paths := []string{"//foo.txt", "///foo.txt", "/../../foo.txt"} for _, path := range paths { @@ -443,7 +448,7 @@ func TestMuxRedirectLeadingSlashes(t *testing.T) { func TestServerTimeouts(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) reqNum := 0 @@ -522,7 +527,7 @@ func TestServerTimeouts(t *testing.T) { // request) that will never happen. func TestOnlyWriteTimeout(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) var conn net.Conn @@ -877,7 +882,7 @@ func TestHeadResponses(t *testing.T) { func TestTLSHandshakeTimeout(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) @@ -1105,6 +1110,7 @@ func TestServerExpect(t *testing.T) { // Under a ~256KB (maxPostHandlerReadBytes) threshold, the server // should consume client request bodies that a handler didn't read. func TestServerUnreadRequestBodyLittle(t *testing.T) { + defer afterTest(t) conn := new(testConn) body := strings.Repeat("x", 100<<10) conn.readBuf.Write([]byte(fmt.Sprintf( @@ -1166,6 +1172,365 @@ func TestServerUnreadRequestBodyLarge(t *testing.T) { } } +type handlerBodyCloseTest struct { + bodySize int + bodyChunked bool + reqConnClose bool + + wantEOFSearch bool // should Handler's Body.Close do Reads, looking for EOF? + wantNextReq bool // should it find the next request on the same conn? +} + +func (t handlerBodyCloseTest) connectionHeader() string { + if t.reqConnClose { + return "Connection: close\r\n" + } + return "" +} + +var handlerBodyCloseTests = [...]handlerBodyCloseTest{ + // Small enough to slurp past to the next request + + // has Content-Length. + 0: { + bodySize: 20 << 10, + bodyChunked: false, + reqConnClose: false, + wantEOFSearch: true, + wantNextReq: true, + }, + + // Small enough to slurp past to the next request + + // is chunked. + 1: { + bodySize: 20 << 10, + bodyChunked: true, + reqConnClose: false, + wantEOFSearch: true, + wantNextReq: true, + }, + + // Small enough to slurp past to the next request + + // has Content-Length + + // declares Connection: close (so pointless to read more). + 2: { + bodySize: 20 << 10, + bodyChunked: false, + reqConnClose: true, + wantEOFSearch: false, + wantNextReq: false, + }, + + // Small enough to slurp past to the next request + + // declares Connection: close, + // but chunked, so it might have trailers. + // TODO: maybe skip this search if no trailers were declared + // in the headers. + 3: { + bodySize: 20 << 10, + bodyChunked: true, + reqConnClose: true, + wantEOFSearch: true, + wantNextReq: false, + }, + + // Big with Content-Length, so give up immediately if we know it's too big. + 4: { + bodySize: 1 << 20, + bodyChunked: false, // has a Content-Length + reqConnClose: false, + wantEOFSearch: false, + wantNextReq: false, + }, + + // Big chunked, so read a bit before giving up. + 5: { + bodySize: 1 << 20, + bodyChunked: true, + reqConnClose: false, + wantEOFSearch: true, + wantNextReq: false, + }, + + // Big with Connection: close, but chunked, so search for trailers. + // TODO: maybe skip this search if no trailers were declared + // in the headers. + 6: { + bodySize: 1 << 20, + bodyChunked: true, + reqConnClose: true, + wantEOFSearch: true, + wantNextReq: false, + }, + + // Big with Connection: close, so don't do any reads on Close. + // With Content-Length. + 7: { + bodySize: 1 << 20, + bodyChunked: false, + reqConnClose: true, + wantEOFSearch: false, + wantNextReq: false, + }, +} + +func TestHandlerBodyClose(t *testing.T) { + for i, tt := range handlerBodyCloseTests { + testHandlerBodyClose(t, i, tt) + } +} + +func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) { + conn := new(testConn) + body := strings.Repeat("x", tt.bodySize) + if tt.bodyChunked { + conn.readBuf.WriteString("POST / HTTP/1.1\r\n" + + "Host: test\r\n" + + tt.connectionHeader() + + "Transfer-Encoding: chunked\r\n" + + "\r\n") + cw := internal.NewChunkedWriter(&conn.readBuf) + io.WriteString(cw, body) + cw.Close() + conn.readBuf.WriteString("\r\n") + } else { + conn.readBuf.Write([]byte(fmt.Sprintf( + "POST / HTTP/1.1\r\n"+ + "Host: test\r\n"+ + tt.connectionHeader()+ + "Content-Length: %d\r\n"+ + "\r\n", len(body)))) + conn.readBuf.Write([]byte(body)) + } + if !tt.reqConnClose { + conn.readBuf.WriteString("GET / HTTP/1.1\r\nHost: test\r\n\r\n") + } + conn.closec = make(chan bool, 1) + + ls := &oneConnListener{conn} + var numReqs int + var size0, size1 int + go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) { + numReqs++ + if numReqs == 1 { + size0 = conn.readBuf.Len() + req.Body.Close() + size1 = conn.readBuf.Len() + } + })) + <-conn.closec + if numReqs < 1 || numReqs > 2 { + t.Fatalf("%d. bug in test. unexpected number of requests = %d", i, numReqs) + } + didSearch := size0 != size1 + if didSearch != tt.wantEOFSearch { + t.Errorf("%d. did EOF search = %v; want %v (size went from %d to %d)", i, didSearch, !didSearch, size0, size1) + } + if tt.wantNextReq && numReqs != 2 { + t.Errorf("%d. numReq = %d; want 2", i, numReqs) + } +} + +// testHandlerBodyConsumer represents a function injected into a test handler to +// vary work done on a request Body. +type testHandlerBodyConsumer struct { + name string + f func(io.ReadCloser) +} + +var testHandlerBodyConsumers = []testHandlerBodyConsumer{ + {"nil", func(io.ReadCloser) {}}, + {"close", func(r io.ReadCloser) { r.Close() }}, + {"discard", func(r io.ReadCloser) { io.Copy(ioutil.Discard, r) }}, +} + +func TestRequestBodyReadErrorClosesConnection(t *testing.T) { + defer afterTest(t) + for _, handler := range testHandlerBodyConsumers { + conn := new(testConn) + conn.readBuf.WriteString("POST /public HTTP/1.1\r\n" + + "Host: test\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n" + + "hax\r\n" + // Invalid chunked encoding + "GET /secret HTTP/1.1\r\n" + + "Host: test\r\n" + + "\r\n") + + conn.closec = make(chan bool, 1) + ls := &oneConnListener{conn} + var numReqs int + go Serve(ls, HandlerFunc(func(_ ResponseWriter, req *Request) { + numReqs++ + if strings.Contains(req.URL.Path, "secret") { + t.Error("Request for /secret encountered, should not have happened.") + } + handler.f(req.Body) + })) + <-conn.closec + if numReqs != 1 { + t.Errorf("Handler %v: got %d reqs; want 1", handler.name, numReqs) + } + } +} + +func TestInvalidTrailerClosesConnection(t *testing.T) { + defer afterTest(t) + for _, handler := range testHandlerBodyConsumers { + conn := new(testConn) + conn.readBuf.WriteString("POST /public HTTP/1.1\r\n" + + "Host: test\r\n" + + "Trailer: hack\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n" + + "3\r\n" + + "hax\r\n" + + "0\r\n" + + "I'm not a valid trailer\r\n" + + "GET /secret HTTP/1.1\r\n" + + "Host: test\r\n" + + "\r\n") + + conn.closec = make(chan bool, 1) + ln := &oneConnListener{conn} + var numReqs int + go Serve(ln, HandlerFunc(func(_ ResponseWriter, req *Request) { + numReqs++ + if strings.Contains(req.URL.Path, "secret") { + t.Errorf("Handler %s, Request for /secret encountered, should not have happened.", handler.name) + } + handler.f(req.Body) + })) + <-conn.closec + if numReqs != 1 { + t.Errorf("Handler %s: got %d reqs; want 1", handler.name, numReqs) + } + } +} + +// slowTestConn is a net.Conn that provides a means to simulate parts of a +// request being received piecemeal. Deadlines can be set and enforced in both +// Read and Write. +type slowTestConn struct { + // over multiple calls to Read, time.Durations are slept, strings are read. + script []interface{} + closec chan bool + rd, wd time.Time // read, write deadline + noopConn +} + +func (c *slowTestConn) SetDeadline(t time.Time) error { + c.SetReadDeadline(t) + c.SetWriteDeadline(t) + return nil +} + +func (c *slowTestConn) SetReadDeadline(t time.Time) error { + c.rd = t + return nil +} + +func (c *slowTestConn) SetWriteDeadline(t time.Time) error { + c.wd = t + return nil +} + +func (c *slowTestConn) Read(b []byte) (n int, err error) { +restart: + if !c.rd.IsZero() && time.Now().After(c.rd) { + return 0, syscall.ETIMEDOUT + } + if len(c.script) == 0 { + return 0, io.EOF + } + + switch cue := c.script[0].(type) { + case time.Duration: + if !c.rd.IsZero() { + // If the deadline falls in the middle of our sleep window, deduct + // part of the sleep, then return a timeout. + if remaining := c.rd.Sub(time.Now()); remaining < cue { + c.script[0] = cue - remaining + time.Sleep(remaining) + return 0, syscall.ETIMEDOUT + } + } + c.script = c.script[1:] + time.Sleep(cue) + goto restart + + case string: + n = copy(b, cue) + // If cue is too big for the buffer, leave the end for the next Read. + if len(cue) > n { + c.script[0] = cue[n:] + } else { + c.script = c.script[1:] + } + + default: + panic("unknown cue in slowTestConn script") + } + + return +} + +func (c *slowTestConn) Close() error { + select { + case c.closec <- true: + default: + } + return nil +} + +func (c *slowTestConn) Write(b []byte) (int, error) { + if !c.wd.IsZero() && time.Now().After(c.wd) { + return 0, syscall.ETIMEDOUT + } + return len(b), nil +} + +func TestRequestBodyTimeoutClosesConnection(t *testing.T) { + if testing.Short() { + t.Skip("skipping in -short mode") + } + defer afterTest(t) + for _, handler := range testHandlerBodyConsumers { + conn := &slowTestConn{ + script: []interface{}{ + "POST /public HTTP/1.1\r\n" + + "Host: test\r\n" + + "Content-Length: 10000\r\n" + + "\r\n", + "foo bar baz", + 600 * time.Millisecond, // Request deadline should hit here + "GET /secret HTTP/1.1\r\n" + + "Host: test\r\n" + + "\r\n", + }, + closec: make(chan bool, 1), + } + ls := &oneConnListener{conn} + + var numReqs int + s := Server{ + Handler: HandlerFunc(func(_ ResponseWriter, req *Request) { + numReqs++ + if strings.Contains(req.URL.Path, "secret") { + t.Error("Request for /secret encountered, should not have happened.") + } + handler.f(req.Body) + }), + ReadTimeout: 400 * time.Millisecond, + } + go s.Serve(ls) + <-conn.closec + + if numReqs != 1 { + t.Errorf("Handler %v: got %d reqs; want 1", handler.name, numReqs) + } + } +} + func TestTimeoutHandler(t *testing.T) { defer afterTest(t) sendHi := make(chan bool, 1) @@ -1451,19 +1816,23 @@ func testHandlerPanic(t *testing.T, withHijack bool, panicValue interface{}) { } } -func TestNoDate(t *testing.T) { +func TestServerNoDate(t *testing.T) { testServerNoHeader(t, "Date") } +func TestServerNoContentType(t *testing.T) { testServerNoHeader(t, "Content-Type") } + +func testServerNoHeader(t *testing.T, header string) { defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header()["Date"] = nil + w.Header()[header] = nil + io.WriteString(w, "<html>foo</html>") // non-empty })) defer ts.Close() res, err := Get(ts.URL) if err != nil { t.Fatal(err) } - _, present := res.Header["Date"] - if present { - t.Fatalf("Expected no Date header; got %v", res.Header["Date"]) + res.Body.Close() + if got, ok := res.Header[header]; ok { + t.Fatalf("Expected no %s header; got %q", header, got) } } @@ -1577,7 +1946,7 @@ func TestRequestBodyLimit(t *testing.T) { // side of their TCP connection, the server doesn't send a 400 Bad Request. func TestClientWriteShutdown(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) @@ -1632,7 +2001,7 @@ func TestServerBufferedChunking(t *testing.T) { // Tests that the server flushes its response headers out when it's // ignoring the response body and waits a bit before forcefully // closing the TCP connection, causing the client to get a RST. -// See http://golang.org/issue/3595 +// See https://golang.org/issue/3595 func TestServerGracefulClose(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -2124,7 +2493,7 @@ func TestDoubleHijack(t *testing.T) { <-conn.closec } -// http://code.google.com/p/go/issues/detail?id=5955 +// https://golang.org/issue/5955 // Note that this does not test the "request too large" // exit path from the http server. This is intentional; // not sending Connection: close is just a minor wire @@ -2288,17 +2657,13 @@ func TestTransportAndServerSharedBodyRace(t *testing.T) { unblockBackend := make(chan bool) backend := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - io.CopyN(rw, req.Body, bodySize/2) + io.CopyN(rw, req.Body, bodySize) <-unblockBackend })) defer backend.Close() backendRespc := make(chan *Response, 1) proxy := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - if req.RequestURI == "/foo" { - rw.Write([]byte("bar")) - return - } req2, _ := NewRequest("POST", backend.URL, req.Body) req2.ContentLength = bodySize @@ -2307,7 +2672,7 @@ func TestTransportAndServerSharedBodyRace(t *testing.T) { t.Errorf("Proxy outbound request: %v", err) return } - _, err = io.CopyN(ioutil.Discard, bresp.Body, bodySize/4) + _, err = io.CopyN(ioutil.Discard, bresp.Body, bodySize/2) if err != nil { t.Errorf("Proxy copy error: %v", err) return @@ -2321,6 +2686,7 @@ func TestTransportAndServerSharedBodyRace(t *testing.T) { })) defer proxy.Close() + defer close(unblockBackend) req, _ := NewRequest("POST", proxy.URL, io.LimitReader(neverEnding('a'), bodySize)) res, err := DefaultClient.Do(req) if err != nil { @@ -2329,8 +2695,12 @@ func TestTransportAndServerSharedBodyRace(t *testing.T) { // Cleanup, so we don't leak goroutines. res.Body.Close() - close(unblockBackend) - (<-backendRespc).Body.Close() + select { + case res := <-backendRespc: + res.Body.Close() + default: + // We failed earlier. (e.g. on DefaultClient.Do(req2)) + } } // Test that a hanging Request.Body.Read from another goroutine can't @@ -2384,19 +2754,24 @@ func TestRequestBodyCloseDoesntBlock(t *testing.T) { } } -func TestResponseWriterWriteStringAllocs(t *testing.T) { - t.Skip("allocs test unreliable with gccgo") +// test that ResponseWriter implements io.stringWriter. +func TestResponseWriterWriteString(t *testing.T) { + okc := make(chan bool, 1) ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { - if r.URL.Path == "/s" { - io.WriteString(w, "Hello world") - } else { - w.Write([]byte("Hello world")) + type stringWriter interface { + WriteString(s string) (n int, err error) } + _, ok := w.(stringWriter) + okc <- ok })) - before := testing.AllocsPerRun(50, func() { ht.rawResponse("GET / HTTP/1.0") }) - after := testing.AllocsPerRun(50, func() { ht.rawResponse("GET /s HTTP/1.0") }) - if int(after) >= int(before) { - t.Errorf("WriteString allocs of %v >= Write allocs of %v", after, before) + ht.rawResponse("GET / HTTP/1.0") + select { + case ok := <-okc: + if !ok { + t.Error("ResponseWriter did not implement io.stringWriter") + } + default: + t.Error("handler was never called") } } @@ -2757,6 +3132,134 @@ func TestServerKeepAliveAfterWriteError(t *testing.T) { } } +// Issue 9987: shouldn't add automatic Content-Length (or +// Content-Type) if a Transfer-Encoding was set by the handler. +func TestNoContentLengthIfTransferEncoding(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Transfer-Encoding", "foo") + io.WriteString(w, "<html>") + })) + defer ts.Close() + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer c.Close() + if _, err := io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"); err != nil { + t.Fatal(err) + } + bs := bufio.NewScanner(c) + var got bytes.Buffer + for bs.Scan() { + if strings.TrimSpace(bs.Text()) == "" { + break + } + got.WriteString(bs.Text()) + got.WriteByte('\n') + } + if err := bs.Err(); err != nil { + t.Fatal(err) + } + if strings.Contains(got.String(), "Content-Length") { + t.Errorf("Unexpected Content-Length in response headers: %s", got.String()) + } + if strings.Contains(got.String(), "Content-Type") { + t.Errorf("Unexpected Content-Type in response headers: %s", got.String()) + } +} + +// tolerate extra CRLF(s) before Request-Line on subsequent requests on a conn +// Issue 10876. +func TestTolerateCRLFBeforeRequestLine(t *testing.T) { + req := []byte("POST / HTTP/1.1\r\nHost: golang.org\r\nContent-Length: 3\r\n\r\nABC" + + "\r\n\r\n" + // <-- this stuff is bogus, but we'll ignore it + "GET / HTTP/1.1\r\nHost: golang.org\r\n\r\n") + var buf bytes.Buffer + conn := &rwTestConn{ + Reader: bytes.NewReader(req), + Writer: &buf, + closec: make(chan bool, 1), + } + ln := &oneConnListener{conn: conn} + numReq := 0 + go Serve(ln, HandlerFunc(func(rw ResponseWriter, r *Request) { + numReq++ + })) + <-conn.closec + if numReq != 2 { + t.Errorf("num requests = %d; want 2", numReq) + t.Logf("Res: %s", buf.Bytes()) + } +} + +func TestIssue11549_Expect100(t *testing.T) { + req := reqBytes(`PUT /readbody HTTP/1.1 +User-Agent: PycURL/7.22.0 +Host: 127.0.0.1:9000 +Accept: */* +Expect: 100-continue +Content-Length: 10 + +HelloWorldPUT /noreadbody HTTP/1.1 +User-Agent: PycURL/7.22.0 +Host: 127.0.0.1:9000 +Accept: */* +Expect: 100-continue +Content-Length: 10 + +GET /should-be-ignored HTTP/1.1 +Host: foo + +`) + var buf bytes.Buffer + conn := &rwTestConn{ + Reader: bytes.NewReader(req), + Writer: &buf, + closec: make(chan bool, 1), + } + ln := &oneConnListener{conn: conn} + numReq := 0 + go Serve(ln, HandlerFunc(func(w ResponseWriter, r *Request) { + numReq++ + if r.URL.Path == "/readbody" { + ioutil.ReadAll(r.Body) + } + io.WriteString(w, "Hello world!") + })) + <-conn.closec + if numReq != 2 { + t.Errorf("num requests = %d; want 2", numReq) + } + if !strings.Contains(buf.String(), "Connection: close\r\n") { + t.Errorf("expected 'Connection: close' in response; got: %s", buf.String()) + } +} + +// If a Handler finishes and there's an unread request body, +// verify the server try to do implicit read on it before replying. +func TestHandlerFinishSkipBigContentLengthRead(t *testing.T) { + conn := &testConn{closec: make(chan bool)} + conn.readBuf.Write([]byte(fmt.Sprintf( + "POST / HTTP/1.1\r\n" + + "Host: test\r\n" + + "Content-Length: 9999999999\r\n" + + "\r\n" + strings.Repeat("a", 1<<20)))) + + ls := &oneConnListener{conn} + var inHandlerLen int + go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) { + inHandlerLen = conn.readBuf.Len() + rw.WriteHeader(404) + })) + <-conn.closec + afterHandlerLen := conn.readBuf.Len() + + if afterHandlerLen != inHandlerLen { + t.Errorf("unexpected implicit read. Read buffer went from %d -> %d", inHandlerLen, afterHandlerLen) + } +} + func BenchmarkClientServer(b *testing.B) { b.ReportAllocs() b.StopTimer() @@ -2886,7 +3389,7 @@ func BenchmarkServer(b *testing.B) { defer ts.Close() b.StartTimer() - cmd := exec.Command(os.Args[0], "-test.run=XXXX", "-test.bench=BenchmarkServer") + cmd := exec.Command(os.Args[0], "-test.run=XXXX", "-test.bench=BenchmarkServer$") cmd.Env = append([]string{ fmt.Sprintf("TEST_BENCH_CLIENT_N=%d", b.N), fmt.Sprintf("TEST_BENCH_SERVER_URL=%s", ts.URL), @@ -2897,6 +3400,95 @@ func BenchmarkServer(b *testing.B) { } } +// getNoBody wraps Get but closes any Response.Body before returning the response. +func getNoBody(urlStr string) (*Response, error) { + res, err := Get(urlStr) + if err != nil { + return nil, err + } + res.Body.Close() + return res, nil +} + +// A benchmark for profiling the client without the HTTP server code. +// The server code runs in a subprocess. +func BenchmarkClient(b *testing.B) { + b.ReportAllocs() + b.StopTimer() + defer afterTest(b) + + port := os.Getenv("TEST_BENCH_SERVER_PORT") // can be set by user + if port == "" { + port = "39207" + } + var data = []byte("Hello world.\n") + if server := os.Getenv("TEST_BENCH_SERVER"); server != "" { + // Server process mode. + HandleFunc("/", func(w ResponseWriter, r *Request) { + r.ParseForm() + if r.Form.Get("stop") != "" { + os.Exit(0) + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(data) + }) + log.Fatal(ListenAndServe("localhost:"+port, nil)) + } + + // Start server process. + cmd := exec.Command(os.Args[0], "-test.run=XXXX", "-test.bench=BenchmarkClient$") + cmd.Env = append(os.Environ(), "TEST_BENCH_SERVER=yes") + if err := cmd.Start(); err != nil { + b.Fatalf("subprocess failed to start: %v", err) + } + defer cmd.Process.Kill() + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + + // Wait for the server process to respond. + url := "http://localhost:" + port + "/" + for i := 0; i < 100; i++ { + time.Sleep(50 * time.Millisecond) + if _, err := getNoBody(url); err == nil { + break + } + if i == 99 { + b.Fatalf("subprocess does not respond") + } + } + + // Do b.N requests to the server. + b.StartTimer() + for i := 0; i < b.N; i++ { + res, err := Get(url) + if err != nil { + b.Fatalf("Get: %v", err) + } + body, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + b.Fatalf("ReadAll: %v", err) + } + if bytes.Compare(body, data) != 0 { + b.Fatalf("Got body: %q", body) + } + } + b.StopTimer() + + // Instruct server process to stop. + getNoBody(url + "?stop=yes") + select { + case err := <-done: + if err != nil { + b.Fatalf("subprocess failed: %v", err) + } + case <-time.After(5 * time.Second): + b.Fatalf("subprocess did not stop") + } +} + func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) { b.ReportAllocs() req := reqBytes(`GET / HTTP/1.0 diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index 008d5aa7a74..a3e43555bb3 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -15,6 +15,7 @@ import ( "io/ioutil" "log" "net" + "net/textproto" "net/url" "os" "path" @@ -55,9 +56,12 @@ type Handler interface { // A ResponseWriter interface is used by an HTTP handler to // construct an HTTP response. type ResponseWriter interface { - // Header returns the header map that will be sent by WriteHeader. - // Changing the header after a call to WriteHeader (or Write) has - // no effect. + // Header returns the header map that will be sent by + // WriteHeader. Changing the header after a call to + // WriteHeader (or Write) has no effect unless the modified + // headers were declared as trailers by setting the + // "Trailer" header before the call to WriteHeader (see example). + // To suppress implicit response headers, set their value to nil. Header() Header // Write writes the data to the connection as part of an HTTP reply. @@ -93,8 +97,14 @@ type Hijacker interface { // Hijack lets the caller take over the connection. // After a call to Hijack(), the HTTP server library // will not do anything else with the connection. + // // It becomes the caller's responsibility to manage // and close the connection. + // + // The returned net.Conn may have read or write deadlines + // already set, depending on the configuration of the + // Server. It is the caller's responsibility to set + // or clear those deadlines as needed. Hijack() (net.Conn, *bufio.ReadWriter, error) } @@ -120,6 +130,7 @@ type conn struct { lr *io.LimitedReader // io.LimitReader(sr) buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->sr->rwc tlsState *tls.ConnectionState // or nil when not using TLS + lastMethod string // method of previous request, or "" mu sync.Mutex // guards the following clientGone bool // if client has disconnected mid-request @@ -188,20 +199,14 @@ func (c *conn) noteClientGone() { c.clientGone = true } -// A switchReader can have its Reader changed at runtime. -// It's not safe for concurrent Reads and switches. -type switchReader struct { - io.Reader -} - // A switchWriter can have its Writer changed at runtime. // It's not safe for concurrent Writes and switches. type switchWriter struct { io.Writer } -// A liveSwitchReader is a switchReader that's safe for concurrent -// reads and switches, if its mutex is held. +// A liveSwitchReader can have its Reader changed at runtime. It's +// safe for concurrent reads and switches, if its mutex is held. type liveSwitchReader struct { sync.Mutex r io.Reader @@ -288,10 +293,21 @@ func (cw *chunkWriter) close() { cw.writeHeader(nil) } if cw.chunking { - // zero EOF chunk, trailer key/value pairs (currently - // unsupported in Go's server), followed by a blank - // line. - cw.res.conn.buf.WriteString("0\r\n\r\n") + bw := cw.res.conn.buf // conn's bufio writer + // zero chunk to mark EOF + bw.WriteString("0\r\n") + if len(cw.res.trailers) > 0 { + trailers := make(Header) + for _, h := range cw.res.trailers { + if vv := cw.res.handlerHeader[h]; len(vv) > 0 { + trailers[h] = vv + } + } + trailers.Write(bw) // the writer handles noting errors + } + // final blank line after the trailers (whether + // present or not) + bw.WriteString("\r\n") } } @@ -332,6 +348,12 @@ type response struct { // input from it. requestBodyLimitHit bool + // trailers are the headers to be sent after the handler + // finishes writing the body. This field is initialized from + // the Trailer response header when the response header is + // written. + trailers []string + handlerDone bool // set true when the handler exits // Buffers for Date and Content-Length @@ -339,6 +361,19 @@ type response struct { clenBuf [10]byte } +// declareTrailer is called for each Trailer header when the +// response header is written. It notes that a header will need to be +// written in the trailers at the end of the response. +func (w *response) declareTrailer(k string) { + k = CanonicalHeaderKey(k) + switch k { + case "Transfer-Encoding", "Content-Length", "Trailer": + // Forbidden by RFC 2616 14.40. + return + } + w.trailers = append(w.trailers, k) +} + // requestTooLarge is called by maxBytesReader when too much input has // been read from the client. func (w *response) requestTooLarge() { @@ -438,7 +473,7 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) { if debugServerConnections { c.rwc = newLoggingConn("server", c.rwc) } - c.sr = liveSwitchReader{r: c.rwc} + c.sr.r = c.rwc c.lr = io.LimitReader(&c.sr, noLimit).(*io.LimitedReader) br := newBufioReader(c.lr) bw := newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) @@ -468,6 +503,8 @@ func newBufioReader(r io.Reader) *bufio.Reader { br.Reset(r) return br } + // Note: if this reader size is every changed, update + // TestHandlerBodyClose's assumptions. return bufio.NewReader(r) } @@ -517,6 +554,7 @@ type expectContinueReader struct { resp *response readCloser io.ReadCloser closed bool + sawEOF bool } func (ecr *expectContinueReader) Read(p []byte) (n int, err error) { @@ -528,7 +566,11 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) { ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n") ecr.resp.conn.buf.Flush() } - return ecr.readCloser.Read(p) + n, err = ecr.readCloser.Read(p) + if err == io.EOF { + ecr.sawEOF = true + } + return } func (ecr *expectContinueReader) Close() error { @@ -582,6 +624,11 @@ func (c *conn) readRequest() (w *response, err error) { } c.lr.N = c.server.initialLimitedReaderSize() + if c.lastMethod == "POST" { + // RFC 2616 section 4.1 tolerance for old buggy clients. + peek, _ := c.buf.Reader.Peek(4) // ReadRequest will get err below + c.buf.Reader.Discard(numLeadingCRorLF(peek)) + } var req *Request if req, err = ReadRequest(c.buf.Reader); err != nil { if c.lr.N == 0 { @@ -590,9 +637,13 @@ func (c *conn) readRequest() (w *response, err error) { return nil, err } c.lr.N = noLimit + c.lastMethod = req.Method req.RemoteAddr = c.remoteAddr req.TLS = c.tlsState + if body, ok := req.Body.(*body); ok { + body.doEarlyClose = true + } w = &response{ conn: c, @@ -747,6 +798,15 @@ func (cw *chunkWriter) writeHeader(p []byte) { } var setHeader extraHeader + trailers := false + for _, v := range cw.header["Trailer"] { + trailers = true + foreachHeaderElement(v, cw.res.declareTrailer) + } + + te := header.get("Transfer-Encoding") + hasTE := te != "" + // If the handler is done but never sent a Content-Length // response header and this is our first (and last) write, set // it, even to zero. This helps HTTP/1.0 clients keep their @@ -759,7 +819,9 @@ func (cw *chunkWriter) writeHeader(p []byte) { // write non-zero bytes. If it's actually 0 bytes and the // handler never looked at the Request.Method, we just don't // send a Content-Length header. - if w.handlerDone && bodyAllowedForStatus(w.status) && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) { + // Further, we don't send an automatic Content-Length if they + // set a Transfer-Encoding, because they're generally incompatible. + if w.handlerDone && !trailers && !hasTE && bodyAllowedForStatus(w.status) && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) { w.contentLength = int64(len(p)) setHeader.contentLength = strconv.AppendInt(cw.res.clenBuf[:0], int64(len(p)), 10) } @@ -789,21 +851,78 @@ func (cw *chunkWriter) writeHeader(p []byte) { w.closeAfterReply = true } + // If the client wanted a 100-continue but we never sent it to + // them (or, more strictly: we never finished reading their + // request body), don't reuse this connection because it's now + // in an unknown state: we might be sending this response at + // the same time the client is now sending its request body + // after a timeout. (Some HTTP clients send Expect: + // 100-continue but knowing that some servers don't support + // it, the clients set a timer and send the body later anyway) + // If we haven't seen EOF, we can't skip over the unread body + // because we don't know if the next bytes on the wire will be + // the body-following-the-timer or the subsequent request. + // See Issue 11549. + if ecr, ok := w.req.Body.(*expectContinueReader); ok && !ecr.sawEOF { + w.closeAfterReply = true + } + // Per RFC 2616, we should consume the request body before // replying, if the handler hasn't already done so. But we // don't want to do an unbounded amount of reading here for // DoS reasons, so we only try up to a threshold. if w.req.ContentLength != 0 && !w.closeAfterReply { - ecr, isExpecter := w.req.Body.(*expectContinueReader) - if !isExpecter || ecr.resp.wroteContinue { - n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1) - if n >= maxPostHandlerReadBytes { - w.requestTooLarge() - delHeader("Connection") - setHeader.connection = "close" - } else { - w.req.Body.Close() + var discard, tooBig bool + + switch bdy := w.req.Body.(type) { + case *expectContinueReader: + if bdy.resp.wroteContinue { + discard = true + } + case *body: + bdy.mu.Lock() + switch { + case bdy.closed: + if !bdy.sawEOF { + // Body was closed in handler with non-EOF error. + w.closeAfterReply = true + } + case bdy.unreadDataSizeLocked() >= maxPostHandlerReadBytes: + tooBig = true + default: + discard = true } + bdy.mu.Unlock() + default: + discard = true + } + + if discard { + _, err := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1) + switch err { + case nil: + // There must be even more data left over. + tooBig = true + case ErrBodyReadAfterClose: + // Body was already consumed and closed. + case io.EOF: + // The remaining body was just consumed, close it. + err = w.req.Body.Close() + if err != nil { + w.closeAfterReply = true + } + default: + // Some other kind of error occured, like a read timeout, or + // corrupt chunked encoding. In any case, whatever remains + // on the wire must not be parsed as another HTTP request. + w.closeAfterReply = true + } + } + + if tooBig { + w.requestTooLarge() + delHeader("Connection") + setHeader.connection = "close" } } @@ -811,7 +930,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { if bodyAllowedForStatus(code) { // If no content type, apply sniffing algorithm to body. _, haveType := header["Content-Type"] - if !haveType { + if !haveType && !hasTE { setHeader.contentType = DetectContentType(p) } } else { @@ -824,8 +943,6 @@ func (cw *chunkWriter) writeHeader(p []byte) { setHeader.date = appendTime(cw.res.dateBuf[:0], time.Now()) } - te := header.get("Transfer-Encoding") - hasTE := te != "" if hasCL && hasTE && te != "identity" { // TODO: return an error if WriteHeader gets a return parameter // For now just ignore the Content-Length. @@ -885,6 +1002,24 @@ func (cw *chunkWriter) writeHeader(p []byte) { w.conn.buf.Write(crlf) } +// foreachHeaderElement splits v according to the "#rule" construction +// in RFC 2616 section 2.1 and calls fn for each non-empty element. +func foreachHeaderElement(v string, fn func(string)) { + v = textproto.TrimString(v) + if v == "" { + return + } + if !strings.Contains(v, ",") { + fn(v) + return + } + for _, f := range strings.Split(v, ",") { + if f = textproto.TrimString(f); f != "" { + fn(f) + } + } +} + // statusLines is a cache of Status-Line strings, keyed by code (for // HTTP/1.1) or negative code (for HTTP/1.0). This is faster than a // map keyed by struct of two fields. This map's max size is bounded @@ -930,7 +1065,7 @@ func statusLine(req *Request, code int) string { return line } -// bodyAllowed returns true if a Write is allowed for this response type. +// bodyAllowed reports whether a Write is allowed for this response type. // It's illegal to call this before the header has been flushed. func (w *response) bodyAllowed() bool { if !w.wroteHeader { @@ -1027,17 +1162,39 @@ func (w *response) finishRequest() { if w.req.MultipartForm != nil { w.req.MultipartForm.RemoveAll() } +} + +// shouldReuseConnection reports whether the underlying TCP connection can be reused. +// It must only be called after the handler is done executing. +func (w *response) shouldReuseConnection() bool { + if w.closeAfterReply { + // The request or something set while executing the + // handler indicated we shouldn't reuse this + // connection. + return false + } if w.req.Method != "HEAD" && w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written { // Did not write enough. Avoid getting out of sync. - w.closeAfterReply = true + return false } // There was some error writing to the underlying connection // during the request, so don't re-use this conn. if w.conn.werr != nil { - w.closeAfterReply = true + return false } + + if w.closedRequestBodyEarly() { + return false + } + + return true +} + +func (w *response) closedRequestBodyEarly() bool { + body, ok := w.req.Body.(*body) + return ok && body.didEarlyClose() } func (w *response) Flush() { @@ -1093,7 +1250,7 @@ var _ closeWriter = (*net.TCPConn)(nil) // pause for a bit, hoping the client processes it before any // subsequent RST. // -// See http://golang.org/issue/3595 +// See https://golang.org/issue/3595 func (c *conn) closeWriteAndWait() { c.finalFlush() if tcp, ok := c.rwc.(closeWriter); ok { @@ -1206,8 +1363,8 @@ func (c *conn) serve() { return } w.finishRequest() - if w.closeAfterReply { - if w.requestBodyLimitHit { + if !w.shouldReuseConnection() { + if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } break @@ -1271,6 +1428,7 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { // The error message should be plain text. func Error(w ResponseWriter, error string, code int) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(code) fmt.Fprintln(w, error) } @@ -1576,7 +1734,8 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) { // strings.Index can't be -1. path = pattern[strings.Index(pattern, "/"):] } - mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern} + url := &url.URL{Path: path} + mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern} } } @@ -1760,11 +1919,11 @@ func (s *Server) doKeepAlives() bool { // By default, keep-alives are always enabled. Only very // resource-constrained environments or servers in the process of // shutting down should disable them. -func (s *Server) SetKeepAlivesEnabled(v bool) { +func (srv *Server) SetKeepAlivesEnabled(v bool) { if v { - atomic.StoreInt32(&s.disableKeepAlives, 0) + atomic.StoreInt32(&srv.disableKeepAlives, 0) } else { - atomic.StoreInt32(&s.disableKeepAlives, 1) + atomic.StoreInt32(&srv.disableKeepAlives, 1) } } @@ -1812,7 +1971,7 @@ func ListenAndServe(addr string, handler Handler) error { // expects HTTPS connections. Additionally, files containing a certificate and // matching private key for the server must be provided. If the certificate // is signed by a certificate authority, the certFile should be the concatenation -// of the server's certificate followed by the CA's certificate. +// of the server's certificate, any intermediates, and the CA's certificate. // // A trivial example server is: // @@ -1844,10 +2003,11 @@ func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Han // ListenAndServeTLS listens on the TCP network address srv.Addr and // then calls Serve to handle requests on incoming TLS connections. // -// Filenames containing a certificate and matching private key for -// the server must be provided. If the certificate is signed by a -// certificate authority, the certFile should be the concatenation -// of the server's certificate followed by the CA's certificate. +// Filenames containing a certificate and matching private key for the +// server must be provided if the Server's TLSConfig.Certificates is +// not populated. If the certificate is signed by a certificate +// authority, the certFile should be the concatenation of the server's +// certificate, any intermediates, and the CA's certificate. // // If srv.Addr is blank, ":https" is used. func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { @@ -1855,19 +2015,18 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { if addr == "" { addr = ":https" } - config := &tls.Config{} - if srv.TLSConfig != nil { - *config = *srv.TLSConfig - } + config := cloneTLSConfig(srv.TLSConfig) if config.NextProtos == nil { config.NextProtos = []string{"http/1.1"} } - var err error - config.Certificates = make([]tls.Certificate, 1) - config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return err + if len(config.Certificates) == 0 || certFile != "" || keyFile != "" { + var err error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } } ln, err := net.Listen("tcp", addr) @@ -2094,3 +2253,15 @@ func (w checkConnErrorWriter) Write(p []byte) (n int, err error) { } return } + +func numLeadingCRorLF(v []byte) (n int) { + for _, b := range v { + if b == '\r' || b == '\n' { + n++ + continue + } + break + } + return + +} diff --git a/libgo/go/net/http/sniff.go b/libgo/go/net/http/sniff.go index 68f519b0542..3be8c865d3b 100644 --- a/libgo/go/net/http/sniff.go +++ b/libgo/go/net/http/sniff.go @@ -38,7 +38,11 @@ func DetectContentType(data []byte) string { } func isWS(b byte) bool { - return bytes.IndexByte([]byte("\t\n\x0C\r "), b) != -1 + switch b { + case '\t', '\n', '\x0c', '\r', ' ': + return true + } + return false } type sniffSig interface { @@ -161,6 +165,8 @@ func (h htmlSig) match(data []byte, firstNonWS int) string { return "text/html; charset=utf-8" } +var mp4ftype = []byte("ftyp") + type mp4Sig int func (mp4Sig) match(data []byte, firstNonWS int) string { @@ -172,7 +178,7 @@ func (mp4Sig) match(data []byte, firstNonWS int) string { if boxSize%4 != 0 || len(data) < boxSize { return "" } - if !bytes.Equal(data[4:8], []byte("ftyp")) { + if !bytes.Equal(data[4:8], mp4ftype) { return "" } for st := 8; st < boxSize; st += 4 { diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index 520500330bc..a8736b28e16 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -27,7 +27,7 @@ type errorReader struct { err error } -func (r *errorReader) Read(p []byte) (n int, err error) { +func (r errorReader) Read(p []byte) (n int, err error) { return 0, r.err } @@ -43,6 +43,7 @@ type transferWriter struct { Close bool TransferEncoding []string Trailer Header + IsResponse bool } func newTransferWriter(r interface{}) (t *transferWriter, err error) { @@ -70,7 +71,7 @@ func newTransferWriter(r interface{}) (t *transferWriter, err error) { n, rerr := io.ReadFull(t.Body, buf[:]) if rerr != nil && rerr != io.EOF { t.ContentLength = -1 - t.Body = &errorReader{rerr} + t.Body = errorReader{rerr} } else if n == 1 { // Oh, guess there is data in this Body Reader after all. // The ContentLength field just wasn't set. @@ -89,6 +90,7 @@ func newTransferWriter(r interface{}) (t *transferWriter, err error) { } } case *Response: + t.IsResponse = true if rr.Request != nil { t.Method = rr.Request.Method } @@ -138,11 +140,17 @@ func (t *transferWriter) shouldSendContentLength() bool { if t.ContentLength > 0 { return true } + if t.ContentLength < 0 { + return false + } // Many servers expect a Content-Length for these methods if t.Method == "POST" || t.Method == "PUT" { return true } if t.ContentLength == 0 && isIdentity(t.TransferEncoding) { + if t.Method == "GET" || t.Method == "HEAD" { + return false + } return true } @@ -203,6 +211,9 @@ func (t *transferWriter) WriteBody(w io.Writer) error { // Write body if t.Body != nil { if chunked(t.TransferEncoding) { + if bw, ok := w.(*bufio.Writer); ok && !t.IsResponse { + w = &internal.FlushAfterChunkWriter{bw} + } cw := internal.NewChunkedWriter(w) _, err = io.Copy(cw, t.Body) if err == nil { @@ -232,7 +243,6 @@ func (t *transferWriter) WriteBody(w io.Writer) error { t.ContentLength, ncopy) } - // TODO(petar): Place trailer writer code here. if chunked(t.TransferEncoding) { // Write Trailer header if t.Trailer != nil { @@ -310,11 +320,13 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { } case *Request: t.Header = rr.Header + t.RequestMethod = rr.Method t.ProtoMajor = rr.ProtoMajor t.ProtoMinor = rr.ProtoMinor // Transfer semantics for Requests are exactly like those for // Responses with status code 200, responding to a GET method t.StatusCode = 200 + t.Close = rr.Close default: panic("unexpected type") } @@ -325,7 +337,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { } // Transfer encoding, content length - t.TransferEncoding, err = fixTransferEncoding(t.RequestMethod, t.Header) + t.TransferEncoding, err = fixTransferEncoding(isResponse, t.RequestMethod, t.Header) if err != nil { return err } @@ -413,12 +425,11 @@ func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" } // Sanitize transfer encoding -func fixTransferEncoding(requestMethod string, header Header) ([]string, error) { +func fixTransferEncoding(isResponse bool, requestMethod string, header Header) ([]string, error) { raw, present := header["Transfer-Encoding"] if !present { return nil, nil } - delete(header, "Transfer-Encoding") encodings := strings.Split(raw[0], ",") @@ -443,9 +454,22 @@ func fixTransferEncoding(requestMethod string, header Header) ([]string, error) return nil, &badStringError{"too many transfer encodings", strings.Join(te, ",")} } if len(te) > 0 { - // Chunked encoding trumps Content-Length. See RFC 2616 - // Section 4.4. Currently len(te) > 0 implies chunked - // encoding. + // RFC 7230 3.3.2 says "A sender MUST NOT send a + // Content-Length header field in any message that + // contains a Transfer-Encoding header field." + // + // but also: + // "If a message is received with both a + // Transfer-Encoding and a Content-Length header + // field, the Transfer-Encoding overrides the + // Content-Length. Such a message might indicate an + // attempt to perform request smuggling (Section 9.5) + // or response splitting (Section 9.4) and ought to be + // handled as an error. A sender MUST remove the + // received Content-Length field prior to forwarding + // such a message downstream." + // + // Reportedly, these appear in the wild. delete(header, "Content-Length") return te, nil } @@ -457,9 +481,17 @@ func fixTransferEncoding(requestMethod string, header Header) ([]string, error) // function is not a method, because ultimately it should be shared by // ReadResponse and ReadRequest. func fixLength(isResponse bool, status int, requestMethod string, header Header, te []string) (int64, error) { - + contentLens := header["Content-Length"] + isRequest := !isResponse // Logic based on response type or status if noBodyExpected(requestMethod) { + // For HTTP requests, as part of hardening against request + // smuggling (RFC 7230), don't allow a Content-Length header for + // methods which don't permit bodies. As an exception, allow + // exactly one Content-Length header if its value is "0". + if isRequest && len(contentLens) > 0 && !(len(contentLens) == 1 && contentLens[0] == "0") { + return 0, fmt.Errorf("http: method cannot contain a Content-Length; got %q", contentLens) + } return 0, nil } if status/100 == 1 { @@ -470,13 +502,21 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header, return 0, nil } + if len(contentLens) > 1 { + // harden against HTTP request smuggling. See RFC 7230. + return 0, errors.New("http: message cannot contain multiple Content-Length headers") + } + // Logic based on Transfer-Encoding if chunked(te) { return -1, nil } // Logic based on Content-Length - cl := strings.TrimSpace(header.get("Content-Length")) + var cl string + if len(contentLens) == 1 { + cl = strings.TrimSpace(contentLens[0]) + } if cl != "" { n, err := parseContentLength(cl) if err != nil { @@ -487,11 +527,14 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header, header.Del("Content-Length") } - if !isResponse && requestMethod == "GET" { - // RFC 2616 doesn't explicitly permit nor forbid an + if !isResponse { + // RFC 2616 neither explicitly permits nor forbids an // entity-body on a GET request so we permit one if // declared, but we default to 0 here (not -1 below) // if there's no mention of a body. + // Likewise, all other request methods are assumed to have + // no body if neither Transfer-Encoding chunked nor a + // Content-Length are set. return 0, nil } @@ -506,14 +549,13 @@ func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool { if major < 1 { return true } else if major == 1 && minor == 0 { - if !strings.Contains(strings.ToLower(header.get("Connection")), "keep-alive") { + vv := header["Connection"] + if headerValuesContainsToken(vv, "close") || !headerValuesContainsToken(vv, "keep-alive") { return true } return false } else { - // TODO: Should split on commas, toss surrounding white space, - // and check each field. - if strings.ToLower(header.get("Connection")) == "close" { + if headerValuesContainsToken(header["Connection"], "close") { if removeCloseHeader { header.Del("Connection") } @@ -555,13 +597,16 @@ func fixTrailer(header Header, te []string) (Header, error) { // Close ensures that the body has been fully read // and then reads the trailer if necessary. type body struct { - src io.Reader - hdr interface{} // non-nil (Response or Request) value means read trailer - r *bufio.Reader // underlying wire-format reader for the trailer - closing bool // is the connection to be closed after reading body? - - mu sync.Mutex // guards closed, and calls to Read and Close - closed bool + src io.Reader + hdr interface{} // non-nil (Response or Request) value means read trailer + r *bufio.Reader // underlying wire-format reader for the trailer + closing bool // is the connection to be closed after reading body? + doEarlyClose bool // whether Close should stop early + + mu sync.Mutex // guards closed, and calls to Read and Close + sawEOF bool + closed bool + earlyClose bool // Close called and we didn't read to the end of src } // ErrBodyReadAfterClose is returned when reading a Request or Response @@ -581,13 +626,23 @@ func (b *body) Read(p []byte) (n int, err error) { // Must hold b.mu. func (b *body) readLocked(p []byte) (n int, err error) { + if b.sawEOF { + return 0, io.EOF + } n, err = b.src.Read(p) if err == io.EOF { + b.sawEOF = true // Chunked case. Read the trailer. if b.hdr != nil { if e := b.readTrailer(); e != nil { err = e + // Something went wrong in the trailer, we must not allow any + // further reads of any kind to succeed from body, nor any + // subsequent requests on the server connection. See + // golang.org/issue/12027 + b.sawEOF = false + b.closed = true } b.hdr = nil } else { @@ -607,6 +662,7 @@ func (b *body) readLocked(p []byte) (n int, err error) { if err == nil && n > 0 { if lr, ok := b.src.(*io.LimitedReader); ok && lr.N == 0 { err = io.EOF + b.sawEOF = true } } @@ -639,8 +695,7 @@ func (b *body) readTrailer() error { // The common case, since nobody uses trailers. buf, err := b.r.Peek(2) if bytes.Equal(buf, singleCRLF) { - b.r.ReadByte() - b.r.ReadByte() + b.r.Discard(2) return nil } if len(buf) < 2 { @@ -688,6 +743,16 @@ func mergeSetHeader(dst *Header, src Header) { } } +// unreadDataSizeLocked returns the number of bytes of unread input. +// It returns -1 if unknown. +// b.mu must be held. +func (b *body) unreadDataSizeLocked() int64 { + if lr, ok := b.src.(*io.LimitedReader); ok { + return lr.N + } + return -1 +} + func (b *body) Close() error { b.mu.Lock() defer b.mu.Unlock() @@ -696,9 +761,30 @@ func (b *body) Close() error { } var err error switch { + case b.sawEOF: + // Already saw EOF, so no need going to look for it. case b.hdr == nil && b.closing: // no trailer and closing the connection next. // no point in reading to EOF. + case b.doEarlyClose: + // Read up to maxPostHandlerReadBytes bytes of the body, looking for + // for EOF (and trailers), so we can re-use this connection. + if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > maxPostHandlerReadBytes { + // There was a declared Content-Length, and we have more bytes remaining + // than our maxPostHandlerReadBytes tolerance. So, give up. + b.earlyClose = true + } else { + var n int64 + // Consume the body, or, which will also lead to us reading + // the trailer headers after the body, if present. + n, err = io.CopyN(ioutil.Discard, bodyLocked{b}, maxPostHandlerReadBytes) + if err == io.EOF { + err = nil + } + if n == maxPostHandlerReadBytes { + b.earlyClose = true + } + } default: // Fully consume the body, which will also lead to us reading // the trailer headers after the body, if present. @@ -708,6 +794,12 @@ func (b *body) Close() error { return err } +func (b *body) didEarlyClose() bool { + b.mu.Lock() + defer b.mu.Unlock() + return b.earlyClose +} + // bodyLocked is a io.Reader reading from a *body when its mutex is // already held. type bodyLocked struct { diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index 782f7cd395b..70d18646059 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -274,11 +274,12 @@ func (t *Transport) CloseIdleConnections() { } } -// CancelRequest cancels an in-flight request by closing its -// connection. +// CancelRequest cancels an in-flight request by closing its connection. +// CancelRequest should only be called after RoundTrip has returned. func (t *Transport) CancelRequest(req *Request) { t.reqMu.Lock() cancel := t.reqCanceler[req] + delete(t.reqCanceler, req) t.reqMu.Unlock() if cancel != nil { cancel() @@ -474,6 +475,25 @@ func (t *Transport) setReqCanceler(r *Request, fn func()) { } } +// replaceReqCanceler replaces an existing cancel function. If there is no cancel function +// for the request, we don't set the function and return false. +// Since CancelRequest will clear the canceler, we can use the return value to detect if +// the request was canceled since the last setReqCancel call. +func (t *Transport) replaceReqCanceler(r *Request, fn func()) bool { + t.reqMu.Lock() + defer t.reqMu.Unlock() + _, ok := t.reqCanceler[r] + if !ok { + return false + } + if fn != nil { + t.reqCanceler[r] = fn + } else { + delete(t.reqCanceler, r) + } + return true +} + func (t *Transport) dial(network, addr string) (c net.Conn, err error) { if t.Dial != nil { return t.Dial(network, addr) @@ -490,6 +510,10 @@ var prePendingDial, postPendingDial func() // is ready to write requests to. func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error) { if pc := t.getIdleConn(cm); pc != nil { + // set request canceler to some non-nil function so we + // can detect whether it was cleared between now and when + // we enter roundTrip + t.setReqCanceler(req, func() {}) return pc, nil } @@ -499,6 +523,11 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error } dialc := make(chan dialRes) + // Copy these hooks so we don't race on the postPendingDial in + // the goroutine we launch. Issue 11136. + prePendingDial := prePendingDial + postPendingDial := postPendingDial + handlePendingDial := func() { if prePendingDial != nil { prePendingDial() @@ -534,6 +563,9 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error // when it finishes: handlePendingDial() return pc, nil + case <-req.Cancel: + handlePendingDial() + return nil, errors.New("net/http: request canceled while waiting for connection") case <-cancelc: handlePendingDial() return nil, errors.New("net/http: request canceled while waiting for connection") @@ -613,16 +645,9 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { if cm.targetScheme == "https" && !tlsDial { // Initiate TLS and check remote host name against certificate. - cfg := t.TLSClientConfig - if cfg == nil || cfg.ServerName == "" { - host := cm.tlsHost() - if cfg == nil { - cfg = &tls.Config{ServerName: host} - } else { - clone := *cfg // shallow clone - clone.ServerName = host - cfg = &clone - } + cfg := cloneTLSClientConfig(t.TLSClientConfig) + if cfg.ServerName == "" { + cfg.ServerName = cm.tlsHost() } plainConn := pconn.conn tlsConn := tls.Client(plainConn, cfg) @@ -662,7 +687,7 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { return pconn, nil } -// useProxy returns true if requests to addr should use a proxy, +// useProxy reports whether requests to addr should use a proxy, // according to the NO_PROXY or no_proxy environment variable. // addr is always a canonicalAddr with a host and port. func useProxy(addr string) bool { @@ -805,6 +830,7 @@ type persistConn struct { numExpectedResponses int closed bool // whether conn has been closed broken bool // an error has happened on this connection; marked broken so it's not reused. + canceled bool // whether this conn was broken due a CancelRequest // mutateHeaderFunc is an optional func to modify extra // headers on each outbound request before it's written. (the // original Request given to RoundTrip is not modified) @@ -819,25 +845,33 @@ func (pc *persistConn) isBroken() bool { return b } -func (pc *persistConn) cancelRequest() { - pc.conn.Close() +// isCanceled reports whether this connection was closed due to CancelRequest. +func (pc *persistConn) isCanceled() bool { + pc.lk.Lock() + defer pc.lk.Unlock() + return pc.canceled } -var remoteSideClosedFunc func(error) bool // or nil to use default - -func remoteSideClosed(err error) bool { - if err == io.EOF { - return true - } - if remoteSideClosedFunc != nil { - return remoteSideClosedFunc(err) - } - return false +func (pc *persistConn) cancelRequest() { + pc.lk.Lock() + defer pc.lk.Unlock() + pc.canceled = true + pc.closeLocked() } func (pc *persistConn) readLoop() { - alive := true + // eofc is used to block http.Handler goroutines reading from Response.Body + // at EOF until this goroutines has (potentially) added the connection + // back to the idle pool. + eofc := make(chan struct{}) + defer close(eofc) // unblock reader on errors + + // Read this once, before loop starts. (to avoid races in tests) + testHookMu.Lock() + testHookReadLoopBeforeNextRead := testHookReadLoopBeforeNextRead + testHookMu.Unlock() + alive := true for alive { pb, err := pc.br.Peek(1) @@ -895,49 +929,79 @@ func (pc *persistConn) readLoop() { alive = false } - var waitForBodyRead chan bool + var waitForBodyRead chan bool // channel is nil when there's no body if hasBody { waitForBodyRead = make(chan bool, 2) resp.Body.(*bodyEOFSignal).earlyCloseFn = func() error { - // Sending false here sets alive to - // false and closes the connection - // below. waitForBodyRead <- false return nil } - resp.Body.(*bodyEOFSignal).fn = func(err error) { - waitForBodyRead <- alive && - err == nil && - !pc.sawEOF && - pc.wroteRequest() && - pc.t.putIdleConn(pc) + resp.Body.(*bodyEOFSignal).fn = func(err error) error { + isEOF := err == io.EOF + waitForBodyRead <- isEOF + if isEOF { + <-eofc // see comment at top + } else if err != nil && pc.isCanceled() { + return errRequestCanceled + } + return err } + } else { + // Before send on rc.ch, as client might re-use the + // same *Request pointer, and we don't want to set this + // on t from this persistConn while the Transport + // potentially spins up a different persistConn for the + // caller's subsequent request. + pc.t.setReqCanceler(rc.req, nil) } - if alive && !hasBody { - alive = !pc.sawEOF && - pc.wroteRequest() && - pc.t.putIdleConn(pc) - } + pc.lk.Lock() + pc.numExpectedResponses-- + pc.lk.Unlock() + // The connection might be going away when we put the + // idleConn below. When that happens, we close the response channel to signal + // to roundTrip that the connection is gone. roundTrip waits for + // both closing and a response in a select, so it might choose + // the close channel, rather than the response. + // We send the response first so that roundTrip can check + // if there is a pending one with a non-blocking select + // on the response channel before erroring out. rc.ch <- responseAndError{resp, err} - // Wait for the just-returned response body to be fully consumed - // before we race and peek on the underlying bufio reader. - if waitForBodyRead != nil { + if hasBody { + // To avoid a race, wait for the just-returned + // response body to be fully consumed before peek on + // the underlying bufio reader. select { - case alive = <-waitForBodyRead: + case <-rc.req.Cancel: + alive = false + pc.t.CancelRequest(rc.req) + case bodyEOF := <-waitForBodyRead: + pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool + alive = alive && + bodyEOF && + !pc.sawEOF && + pc.wroteRequest() && + pc.t.putIdleConn(pc) + if bodyEOF { + eofc <- struct{}{} + } case <-pc.closech: alive = false } + } else { + alive = alive && + !pc.sawEOF && + pc.wroteRequest() && + pc.t.putIdleConn(pc) } - pc.t.setReqCanceler(rc.req, nil) - - if !alive { - pc.close() + if hook := testHookReadLoopBeforeNextRead; hook != nil { + hook() } } + pc.close() } func (pc *persistConn) writeLoop() { @@ -1027,9 +1091,24 @@ func (e *httpError) Temporary() bool { return true } var errTimeout error = &httpError{err: "net/http: timeout awaiting response headers", timeout: true} var errClosed error = &httpError{err: "net/http: transport closed before response was received"} +var errRequestCanceled = errors.New("net/http: request canceled") + +// nil except for tests +var ( + testHookPersistConnClosedGotRes func() + testHookEnterRoundTrip func() + testHookMu sync.Locker = fakeLocker{} // guards following + testHookReadLoopBeforeNextRead func() +) func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) { - pc.t.setReqCanceler(req.Request, pc.cancelRequest) + if hook := testHookEnterRoundTrip; hook != nil { + hook() + } + if !pc.t.replaceReqCanceler(req.Request, pc.cancelRequest) { + pc.t.putIdleConn(pc) + return nil, errRequestCanceled + } pc.lk.Lock() pc.numExpectedResponses++ headerFn := pc.mutateHeaderFunc @@ -1055,15 +1134,19 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err // Note that we don't request this for HEAD requests, // due to a bug in nginx: // http://trac.nginx.org/nginx/ticket/358 - // http://golang.org/issue/5522 + // https://golang.org/issue/5522 // // We don't request gzip if the request is for a range, since // auto-decoding a portion of a gzipped document will just fail - // anyway. See http://golang.org/issue/8923 + // anyway. See https://golang.org/issue/8923 requestedGzip = true req.extraHeaders().Set("Accept-Encoding", "gzip") } + if pc.t.DisableKeepAlives { + req.extraHeaders().Set("Connection", "close") + } + // Write the request concurrently with waiting for a response, // in case the server decides to reply before reading our full // request body. @@ -1074,38 +1157,57 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err pc.reqch <- requestAndChan{req.Request, resc, requestedGzip} var re responseAndError - var pconnDeadCh = pc.closech - var failTicker <-chan time.Time var respHeaderTimer <-chan time.Time + cancelChan := req.Request.Cancel WaitResponse: for { select { case err := <-writeErrCh: + if isNetWriteError(err) { + // Issue 11745. If we failed to write the request + // body, it's possible the server just heard enough + // and already wrote to us. Prioritize the server's + // response over returning a body write error. + select { + case re = <-resc: + pc.close() + break WaitResponse + case <-time.After(50 * time.Millisecond): + // Fall through. + } + } if err != nil { re = responseAndError{nil, err} pc.close() break WaitResponse } if d := pc.t.ResponseHeaderTimeout; d > 0 { - respHeaderTimer = time.After(d) + timer := time.NewTimer(d) + defer timer.Stop() // prevent leaks + respHeaderTimer = timer.C } - case <-pconnDeadCh: + case <-pc.closech: // The persist connection is dead. This shouldn't // usually happen (only with Connection: close responses // with no response bodies), but if it does happen it // means either a) the remote server hung up on us // prematurely, or b) the readLoop sent us a response & // closed its closech at roughly the same time, and we - // selected this case first, in which case a response - // might still be coming soon. - // - // We can't avoid the select race in b) by using a unbuffered - // resc channel instead, because then goroutines can - // leak if we exit due to other errors. - pconnDeadCh = nil // avoid spinning - failTicker = time.After(100 * time.Millisecond) // arbitrary time to wait for resc - case <-failTicker: - re = responseAndError{err: errClosed} + // selected this case first. If we got a response, readLoop makes sure + // to send it before it puts the conn and closes the channel. + // That way, we can fetch the response, if there is one, + // with a non-blocking receive. + select { + case re = <-resc: + if fn := testHookPersistConnClosedGotRes; fn != nil { + fn() + } + default: + re = responseAndError{err: errClosed} + if pc.isCanceled() { + re = responseAndError{err: errRequestCanceled} + } + } break WaitResponse case <-respHeaderTimer: pc.close() @@ -1113,13 +1215,12 @@ WaitResponse: break WaitResponse case re = <-resc: break WaitResponse + case <-cancelChan: + pc.t.CancelRequest(req.Request) + cancelChan = nil } } - pc.lk.Lock() - pc.numExpectedResponses-- - pc.lk.Unlock() - if re.err != nil { pc.t.setReqCanceler(req.Request, nil) } @@ -1167,16 +1268,18 @@ func canonicalAddr(url *url.URL) string { // bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most // once, right before its final (error-producing) Read or Close call -// returns. If earlyCloseFn is non-nil and Close is called before -// io.EOF is seen, earlyCloseFn is called instead of fn, and its -// return value is the return value from Close. +// returns. fn should return the new error to return from Read or Close. +// +// If earlyCloseFn is non-nil and Close is called before io.EOF is +// seen, earlyCloseFn is called instead of fn, and its return value is +// the return value from Close. type bodyEOFSignal struct { body io.ReadCloser - mu sync.Mutex // guards following 4 fields - closed bool // whether Close has been called - rerr error // sticky Read error - fn func(error) // error will be nil on Read io.EOF - earlyCloseFn func() error // optional alt Close func used if io.EOF not seen + mu sync.Mutex // guards following 4 fields + closed bool // whether Close has been called + rerr error // sticky Read error + fn func(error) error // err will be nil on Read io.EOF + earlyCloseFn func() error // optional alt Close func used if io.EOF not seen } func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { @@ -1197,7 +1300,7 @@ func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { if es.rerr == nil { es.rerr = err } - es.condfn(err) + err = es.condfn(err) } return } @@ -1213,20 +1316,17 @@ func (es *bodyEOFSignal) Close() error { return es.earlyCloseFn() } err := es.body.Close() - es.condfn(err) - return err + return es.condfn(err) } // caller must hold es.mu. -func (es *bodyEOFSignal) condfn(err error) { +func (es *bodyEOFSignal) condfn(err error) error { if es.fn == nil { - return - } - if err == io.EOF { - err = nil + return err } - es.fn(err) + err = es.fn(err) es.fn = nil + return err } // gzipReader wraps a response body so it can lazily @@ -1273,3 +1373,89 @@ func (nr noteEOFReader) Read(p []byte) (n int, err error) { } return } + +// fakeLocker is a sync.Locker which does nothing. It's used to guard +// test-only fields when not under test, to avoid runtime atomic +// overhead. +type fakeLocker struct{} + +func (fakeLocker) Lock() {} +func (fakeLocker) Unlock() {} + +func isNetWriteError(err error) bool { + switch e := err.(type) { + case *url.Error: + return isNetWriteError(e.Err) + case *net.OpError: + return e.Op == "write" + default: + return false + } +} + +// cloneTLSConfig returns a shallow clone of the exported +// fields of cfg, ignoring the unexported sync.Once, which +// contains a mutex and must not be copied. +// +// The cfg must not be in active use by tls.Server, or else +// there can still be a race with tls.Server updating SessionTicketKey +// and our copying it, and also a race with the server setting +// SessionTicketsDisabled=false on failure to set the random +// ticket key. +// +// If cfg is nil, a new zero tls.Config is returned. +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + if cfg == nil { + return &tls.Config{} + } + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + SessionTicketsDisabled: cfg.SessionTicketsDisabled, + SessionTicketKey: cfg.SessionTicketKey, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} + +// cloneTLSClientConfig is like cloneTLSConfig but omits +// the fields SessionTicketsDisabled and SessionTicketKey. +// This makes it safe to call cloneTLSClientConfig on a config +// in active use by a server. +func cloneTLSClientConfig(cfg *tls.Config) *tls.Config { + if cfg == nil { + return &tls.Config{} + } + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index defa6337082..c21d4afa87f 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -18,11 +18,11 @@ import ( "io/ioutil" "log" "net" - "net/http" . "net/http" "net/http/httptest" "net/url" "os" + "reflect" "runtime" "strconv" "strings" @@ -39,6 +39,7 @@ var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) { if r.FormValue("close") == "true" { w.Header().Set("Connection", "close") } + w.Header().Set("X-Saw-Close", fmt.Sprint(r.Close)) w.Write([]byte(r.RemoteAddr)) }) @@ -228,6 +229,10 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) { if err != nil { t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err) } + if got, want := res.Header.Get("X-Saw-Close"), fmt.Sprint(connectionClose); got != want { + t.Errorf("For connectionClose = %v; handler's X-Saw-Close was %v; want %v", + connectionClose, got, !connectionClose) + } body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err) @@ -249,6 +254,27 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) { connSet.check(t) } +// if the Transport's DisableKeepAlives is set, all requests should +// send Connection: close. +func TestTransportConnectionCloseOnRequestDisableKeepAlive(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(hostPortHandler) + defer ts.Close() + + tr := &Transport{ + DisableKeepAlives: true, + } + c := &Client{Transport: tr} + res, err := c.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + res.Body.Close() + if res.Header.Get("X-Saw-Close") != "true" { + t.Errorf("handler didn't see Connection: close ") + } +} + func TestTransportIdleCacheKeys(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) @@ -293,7 +319,7 @@ func TestTransportReadToEndReusesConn(t *testing.T) { addrSeen[r.RemoteAddr]++ if r.URL.Path == "/chunked/" { w.WriteHeader(200) - w.(http.Flusher).Flush() + w.(Flusher).Flush() } else { w.Header().Set("Content-Type", strconv.Itoa(len(msg))) w.WriteHeader(200) @@ -308,7 +334,7 @@ func TestTransportReadToEndReusesConn(t *testing.T) { wantLen := []int{len(msg), -1}[pi] addrSeen = make(map[string]int) for i := 0; i < 3; i++ { - res, err := http.Get(ts.URL + path) + res, err := Get(ts.URL + path) if err != nil { t.Errorf("Get %s: %v", path, err) continue @@ -459,7 +485,7 @@ func TestTransportServerClosingUnexpectedly(t *testing.T) { } } -// Test for http://golang.org/issue/2616 (appropriate issue number) +// Test for https://golang.org/issue/2616 (appropriate issue number) // This fails pretty reliably with GOMAXPROCS=100 or something high. func TestStressSurpriseServerCloses(t *testing.T) { defer afterTest(t) @@ -479,12 +505,17 @@ func TestStressSurpriseServerCloses(t *testing.T) { tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} + defer tr.CloseIdleConnections() // Do a bunch of traffic from different goroutines. Send to activityc // after each request completes, regardless of whether it failed. + // If these are too high, OS X exhausts its ephemeral ports + // and hangs waiting for them to transition TCP states. That's + // not what we want to test. TODO(bradfitz): use an io.Pipe + // dialer for this test instead? const ( - numClients = 50 - reqsPerClient = 250 + numClients = 20 + reqsPerClient = 25 ) activityc := make(chan bool) for i := 0; i < numClients; i++ { @@ -567,11 +598,22 @@ func TestTransportHeadChunkedResponse(t *testing.T) { tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} + // Ensure that we wait for the readLoop to complete before + // calling Head again + didRead := make(chan bool) + SetReadLoopBeforeNextReadHook(func() { didRead <- true }) + defer SetReadLoopBeforeNextReadHook(nil) + res1, err := c.Head(ts.URL) + <-didRead + if err != nil { t.Fatalf("request 1 error: %v", err) } + res2, err := c.Head(ts.URL) + <-didRead + if err != nil { t.Fatalf("request 2 error: %v", err) } @@ -833,7 +875,7 @@ func TestTransportGzipShort(t *testing.T) { // tests that persistent goroutine connections shut down when no longer desired. func TestTransportPersistConnLeak(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) gotReqCh := make(chan bool) @@ -902,7 +944,7 @@ func TestTransportPersistConnLeak(t *testing.T) { // request.ContentLength is explicitly short func TestTransportPersistConnLeakShortBody(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -941,7 +983,7 @@ func TestTransportPersistConnLeakShortBody(t *testing.T) { } } -// This used to crash; http://golang.org/issue/3266 +// This used to crash; https://golang.org/issue/3266 func TestTransportIdleConnCrash(t *testing.T) { defer afterTest(t) tr := &Transport{} @@ -1023,7 +1065,7 @@ func TestIssue3595(t *testing.T) { } } -// From http://golang.org/issue/4454 , +// From https://golang.org/issue/4454 , // "client fails to handle requests with no body and chunked encoding" func TestChunkedNoContent(t *testing.T) { defer afterTest(t) @@ -1110,7 +1152,7 @@ func TestTransportConcurrency(t *testing.T) { func TestIssue4191_InfiniteGetTimeout(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) const debug = false @@ -1174,7 +1216,7 @@ func TestIssue4191_InfiniteGetTimeout(t *testing.T) { func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") + t.Skip("skipping test; see https://golang.org/issue/7237") } defer afterTest(t) const debug = false @@ -1345,8 +1387,8 @@ func TestTransportCancelRequest(t *testing.T) { body, err := ioutil.ReadAll(res.Body) d := time.Since(t0) - if err == nil { - t.Error("expected an error reading the body") + if err != ExportErrRequestCanceled { + t.Errorf("Body.Read error = %v; want errRequestCanceled", err) } if string(body) != "Hello" { t.Errorf("Body = %q; want Hello", body) @@ -1356,7 +1398,7 @@ func TestTransportCancelRequest(t *testing.T) { } // Verify no outstanding requests after readLoop/writeLoop // goroutines shut down. - for tries := 3; tries > 0; tries-- { + for tries := 5; tries > 0; tries-- { n := tr.NumPendingRequestsForTesting() if n == 0 { break @@ -1405,6 +1447,7 @@ func TestTransportCancelRequestInDial(t *testing.T) { eventLog.Printf("canceling") tr.CancelRequest(req) + tr.CancelRequest(req) // used to panic on second call select { case <-gotres: @@ -1422,6 +1465,135 @@ Get = Get http://something.no-network.tld/: net/http: request canceled while wai } } +func TestCancelRequestWithChannel(t *testing.T) { + defer afterTest(t) + if testing.Short() { + t.Skip("skipping test in -short mode") + } + unblockc := make(chan bool) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + fmt.Fprintf(w, "Hello") + w.(Flusher).Flush() // send headers and some body + <-unblockc + })) + defer ts.Close() + defer close(unblockc) + + tr := &Transport{} + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + + req, _ := NewRequest("GET", ts.URL, nil) + ch := make(chan struct{}) + req.Cancel = ch + + res, err := c.Do(req) + if err != nil { + t.Fatal(err) + } + go func() { + time.Sleep(1 * time.Second) + close(ch) + }() + t0 := time.Now() + body, err := ioutil.ReadAll(res.Body) + d := time.Since(t0) + + if err != ExportErrRequestCanceled { + t.Errorf("Body.Read error = %v; want errRequestCanceled", err) + } + if string(body) != "Hello" { + t.Errorf("Body = %q; want Hello", body) + } + if d < 500*time.Millisecond { + t.Errorf("expected ~1 second delay; got %v", d) + } + // Verify no outstanding requests after readLoop/writeLoop + // goroutines shut down. + for tries := 5; tries > 0; tries-- { + n := tr.NumPendingRequestsForTesting() + if n == 0 { + break + } + time.Sleep(100 * time.Millisecond) + if tries == 1 { + t.Errorf("pending requests = %d; want 0", n) + } + } +} + +func TestCancelRequestWithChannelBeforeDo(t *testing.T) { + defer afterTest(t) + unblockc := make(chan bool) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + <-unblockc + })) + defer ts.Close() + defer close(unblockc) + + // Don't interfere with the next test on plan9. + // Cf. https://golang.org/issues/11476 + if runtime.GOOS == "plan9" { + defer time.Sleep(500 * time.Millisecond) + } + + tr := &Transport{} + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + + req, _ := NewRequest("GET", ts.URL, nil) + ch := make(chan struct{}) + req.Cancel = ch + close(ch) + + _, err := c.Do(req) + if err == nil || !strings.Contains(err.Error(), "canceled") { + t.Errorf("Do error = %v; want cancelation", err) + } +} + +// Issue 11020. The returned error message should be errRequestCanceled +func TestTransportCancelBeforeResponseHeaders(t *testing.T) { + t.Skip("Skipping flaky test; see Issue 11894") + defer afterTest(t) + + serverConnCh := make(chan net.Conn, 1) + tr := &Transport{ + Dial: func(network, addr string) (net.Conn, error) { + cc, sc := net.Pipe() + serverConnCh <- sc + return cc, nil + }, + } + defer tr.CloseIdleConnections() + errc := make(chan error, 1) + req, _ := NewRequest("GET", "http://example.com/", nil) + go func() { + _, err := tr.RoundTrip(req) + errc <- err + }() + + sc := <-serverConnCh + verb := make([]byte, 3) + if _, err := io.ReadFull(sc, verb); err != nil { + t.Errorf("Error reading HTTP verb from server: %v", err) + } + if string(verb) != "GET" { + t.Errorf("server received %q; want GET", verb) + } + defer sc.Close() + + tr.CancelRequest(req) + + err := <-errc + if err == nil { + t.Fatalf("unexpected success from RoundTrip") + } + if err != ExportErrRequestCanceled { + t.Errorf("RoundTrip error = %v; want ExportErrRequestCanceled", err) + } +} + // golang.org/issue/3672 -- Client can't close HTTP stream // Calling Close on a Response.Body used to just read until EOF. // Now it actually closes the TCP connection. @@ -1795,6 +1967,11 @@ func TestIdleConnChannelLeak(t *testing.T) { })) defer ts.Close() + const nReqs = 5 + didRead := make(chan bool, nReqs) + SetReadLoopBeforeNextReadHook(func() { didRead <- true }) + defer SetReadLoopBeforeNextReadHook(nil) + tr := &Transport{ Dial: func(netw, addr string) (net.Conn, error) { return net.Dial(netw, ts.Listener.Addr().String()) @@ -1807,12 +1984,28 @@ func TestIdleConnChannelLeak(t *testing.T) { // First, without keep-alives. for _, disableKeep := range []bool{true, false} { tr.DisableKeepAlives = disableKeep - for i := 0; i < 5; i++ { + for i := 0; i < nReqs; i++ { _, err := c.Get(fmt.Sprintf("http://foo-host-%d.tld/", i)) if err != nil { t.Fatal(err) } + // Note: no res.Body.Close is needed here, since the + // response Content-Length is zero. Perhaps the test + // should be more explicit and use a HEAD, but tests + // elsewhere guarantee that zero byte responses generate + // a "Content-Length: 0" instead of chunking. + } + + // At this point, each of the 5 Transport.readLoop goroutines + // are scheduling noting that there are no response bodies (see + // earlier comment), and are then calling putIdleConn, which + // decrements this count. Usually that happens quickly, which is + // why this test has seemed to work for ages. But it's still + // racey: we have wait for them to finish first. See Issue 10427 + for i := 0; i < nReqs; i++ { + <-didRead } + if got := tr.IdleConnChMapSizeForTesting(); got != 0 { t.Fatalf("ForDisableKeepAlives = %v, map size = %d; want 0", disableKeep, got) } @@ -1824,7 +2017,7 @@ func TestIdleConnChannelLeak(t *testing.T) { // then closes it. func TestTransportClosesRequestBody(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(http.HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { io.Copy(ioutil.Discard, r.Body) })) defer ts.Close() @@ -2060,6 +2253,38 @@ func TestTransportNoReuseAfterEarlyResponse(t *testing.T) { } } +// Tests that we don't leak Transport persistConn.readLoop goroutines +// when a server hangs up immediately after saying it would keep-alive. +func TestTransportIssue10457(t *testing.T) { + defer afterTest(t) // used to fail in goroutine leak check + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + // Send a response with no body, keep-alive + // (implicit), and then lie and immediately close the + // connection. This forces the Transport's readLoop to + // immediately Peek an io.EOF and get to the point + // that used to hang. + conn, _, _ := w.(Hijacker).Hijack() + conn.Write([]byte("HTTP/1.1 200 OK\r\nFoo: Bar\r\nContent-Length: 0\r\n\r\n")) // keep-alive + conn.Close() + })) + defer ts.Close() + tr := &Transport{} + defer tr.CloseIdleConnections() + cl := &Client{Transport: tr} + res, err := cl.Get(ts.URL) + if err != nil { + t.Fatalf("Get: %v", err) + } + defer res.Body.Close() + + // Just a sanity check that we at least get the response. The real + // test here is that the "defer afterTest" above doesn't find any + // leaked goroutines. + if got, want := res.Header.Get("Foo"), "Bar"; got != want { + t.Errorf("Foo header = %q; want %q", got, want) + } +} + type errorReader struct { err error } @@ -2073,7 +2298,7 @@ func (f closerFunc) Close() error { return f() } // Issue 6981 func TestTransportClosesBodyOnError(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7782") + t.Skip("skipping test; see https://golang.org/issue/7782") } defer afterTest(t) readBody := make(chan error, 1) @@ -2162,13 +2387,13 @@ func TestTransportDialTLS(t *testing.T) { // Test for issue 8755 // Ensure that if a proxy returns an error, it is exposed by RoundTrip func TestRoundTripReturnsProxyError(t *testing.T) { - badProxy := func(*http.Request) (*url.URL, error) { + badProxy := func(*Request) (*url.URL, error) { return nil, errors.New("errorMessage") } tr := &Transport{Proxy: badProxy} - req, _ := http.NewRequest("GET", "http://example.com", nil) + req, _ := NewRequest("GET", "http://example.com", nil) _, err := tr.RoundTrip(req) @@ -2249,7 +2474,268 @@ func TestTransportRangeAndGzip(t *testing.T) { res.Body.Close() } -func wantBody(res *http.Response, err error, want string) error { +// Previously, we used to handle a logical race within RoundTrip by waiting for 100ms +// in the case of an error. Changing the order of the channel operations got rid of this +// race. +// +// In order to test that the channel op reordering works, we install a hook into the +// roundTrip function which gets called if we saw the connection go away and +// we subsequently received a response. +func TestTransportResponseCloseRace(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + defer afterTest(t) + + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + })) + defer ts.Close() + sawRace := false + SetInstallConnClosedHook(func() { + sawRace = true + }) + defer SetInstallConnClosedHook(nil) + tr := &Transport{ + DisableKeepAlives: true, + } + req, err := NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) + } + // selects are not deterministic, so do this a bunch + // and see if we handle the logical race at least once. + for i := 0; i < 10000; i++ { + resp, err := tr.RoundTrip(req) + if err != nil { + t.Fatalf("unexpected error: %s", err) + continue + } + resp.Body.Close() + if sawRace { + break + } + } + if !sawRace { + t.Errorf("didn't see response/connection going away race") + } +} + +// Test for issue 10474 +func TestTransportResponseCancelRace(t *testing.T) { + defer afterTest(t) + + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + // important that this response has a body. + var b [1024]byte + w.Write(b[:]) + })) + defer ts.Close() + + tr := &Transport{} + defer tr.CloseIdleConnections() + + req, err := NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) + } + res, err := tr.RoundTrip(req) + if err != nil { + t.Fatal(err) + } + // If we do an early close, Transport just throws the connection away and + // doesn't reuse it. In order to trigger the bug, it has to reuse the connection + // so read the body + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + t.Fatal(err) + } + + req2, err := NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) + } + tr.CancelRequest(req) + res, err = tr.RoundTrip(req2) + if err != nil { + t.Fatal(err) + } + res.Body.Close() +} + +func TestTransportDialCancelRace(t *testing.T) { + defer afterTest(t) + + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) + defer ts.Close() + + tr := &Transport{} + defer tr.CloseIdleConnections() + + req, err := NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) + } + SetEnterRoundTripHook(func() { + tr.CancelRequest(req) + }) + defer SetEnterRoundTripHook(nil) + res, err := tr.RoundTrip(req) + if err != ExportErrRequestCanceled { + t.Errorf("expected canceled request error; got %v", err) + if err == nil { + res.Body.Close() + } + } +} + +// logWritesConn is a net.Conn that logs each Write call to writes +// and then proxies to w. +// It proxies Read calls to a reader it receives from rch. +type logWritesConn struct { + net.Conn // nil. crash on use. + + w io.Writer + + rch <-chan io.Reader + r io.Reader // nil until received by rch + + mu sync.Mutex + writes []string +} + +func (c *logWritesConn) Write(p []byte) (n int, err error) { + c.mu.Lock() + defer c.mu.Unlock() + c.writes = append(c.writes, string(p)) + return c.w.Write(p) +} + +func (c *logWritesConn) Read(p []byte) (n int, err error) { + if c.r == nil { + c.r = <-c.rch + } + return c.r.Read(p) +} + +func (c *logWritesConn) Close() error { return nil } + +// Issue 6574 +func TestTransportFlushesBodyChunks(t *testing.T) { + defer afterTest(t) + resBody := make(chan io.Reader, 1) + connr, connw := io.Pipe() // connection pipe pair + lw := &logWritesConn{ + rch: resBody, + w: connw, + } + tr := &Transport{ + Dial: func(network, addr string) (net.Conn, error) { + return lw, nil + }, + } + bodyr, bodyw := io.Pipe() // body pipe pair + go func() { + defer bodyw.Close() + for i := 0; i < 3; i++ { + fmt.Fprintf(bodyw, "num%d\n", i) + } + }() + resc := make(chan *Response) + go func() { + req, _ := NewRequest("POST", "http://localhost:8080", bodyr) + req.Header.Set("User-Agent", "x") // known value for test + res, err := tr.RoundTrip(req) + if err != nil { + t.Error("RoundTrip: %v", err) + close(resc) + return + } + resc <- res + + }() + // Fully consume the request before checking the Write log vs. want. + req, err := ReadRequest(bufio.NewReader(connr)) + if err != nil { + t.Fatal(err) + } + io.Copy(ioutil.Discard, req.Body) + + // Unblock the transport's roundTrip goroutine. + resBody <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n") + res, ok := <-resc + if !ok { + return + } + defer res.Body.Close() + + want := []string{ + // Because Request.ContentLength = 0, the body is sniffed for 1 byte to determine whether there's content. + // That explains the initial "num0" being split into "n" and "um0". + // The first byte is included with the request headers Write. Perhaps in the future + // we will want to flush the headers out early if the first byte of the request body is + // taking a long time to arrive. But not yet. + "POST / HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: x\r\nTransfer-Encoding: chunked\r\nAccept-Encoding: gzip\r\n\r\n" + + "1\r\nn\r\n", + "4\r\num0\n\r\n", + "5\r\nnum1\n\r\n", + "5\r\nnum2\n\r\n", + "0\r\n\r\n", + } + if !reflect.DeepEqual(lw.writes, want) { + t.Errorf("Writes differed.\n Got: %q\nWant: %q\n", lw.writes, want) + } +} + +// Issue 11745. +func TestTransportPrefersResponseOverWriteError(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + defer afterTest(t) + const contentLengthLimit = 1024 * 1024 // 1MB + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + if r.ContentLength >= contentLengthLimit { + w.WriteHeader(StatusBadRequest) + r.Body.Close() + return + } + w.WriteHeader(StatusOK) + })) + defer ts.Close() + + fail := 0 + count := 100 + bigBody := strings.Repeat("a", contentLengthLimit*2) + for i := 0; i < count; i++ { + req, err := NewRequest("PUT", ts.URL, strings.NewReader(bigBody)) + if err != nil { + t.Fatal(err) + } + tr := new(Transport) + defer tr.CloseIdleConnections() + client := &Client{Transport: tr} + resp, err := client.Do(req) + if err != nil { + fail++ + t.Logf("%d = %#v", i, err) + if ue, ok := err.(*url.Error); ok { + t.Logf("urlErr = %#v", ue.Err) + if ne, ok := ue.Err.(*net.OpError); ok { + t.Logf("netOpError = %#v", ne.Err) + } + } + } else { + resp.Body.Close() + if resp.StatusCode != 400 { + t.Errorf("Expected status code 400, got %v", resp.Status) + } + } + } + if fail > 0 { + t.Errorf("Failed %v out of %v\n", fail, count) + } +} + +func wantBody(res *Response, err error, want string) error { if err != nil { return err } diff --git a/libgo/go/net/interface.go b/libgo/go/net/interface.go index 2e9f1ebc679..9c7b5da8fe9 100644 --- a/libgo/go/net/interface.go +++ b/libgo/go/net/interface.go @@ -62,41 +62,61 @@ func (f Flags) String() string { // Addrs returns interface addresses for a specific interface. func (ifi *Interface) Addrs() ([]Addr, error) { if ifi == nil { - return nil, errInvalidInterface + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterface} } - return interfaceAddrTable(ifi) + ifat, err := interfaceAddrTable(ifi) + if err != nil { + err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} + } + return ifat, err } // MulticastAddrs returns multicast, joined group addresses for // a specific interface. func (ifi *Interface) MulticastAddrs() ([]Addr, error) { if ifi == nil { - return nil, errInvalidInterface + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterface} + } + ifat, err := interfaceMulticastAddrTable(ifi) + if err != nil { + err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} } - return interfaceMulticastAddrTable(ifi) + return ifat, err } // Interfaces returns a list of the system's network interfaces. func Interfaces() ([]Interface, error) { - return interfaceTable(0) + ift, err := interfaceTable(0) + if err != nil { + err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} + } + return ift, err } // InterfaceAddrs returns a list of the system's network interface // addresses. func InterfaceAddrs() ([]Addr, error) { - return interfaceAddrTable(nil) + ifat, err := interfaceAddrTable(nil) + if err != nil { + err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} + } + return ifat, err } // InterfaceByIndex returns the interface specified by index. func InterfaceByIndex(index int) (*Interface, error) { if index <= 0 { - return nil, errInvalidInterfaceIndex + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceIndex} } ift, err := interfaceTable(index) if err != nil { - return nil, err + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} } - return interfaceByIndex(ift, index) + ifi, err := interfaceByIndex(ift, index) + if err != nil { + err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} + } + return ifi, err } func interfaceByIndex(ift []Interface, index int) (*Interface, error) { @@ -111,16 +131,16 @@ func interfaceByIndex(ift []Interface, index int) (*Interface, error) { // InterfaceByName returns the interface specified by name. func InterfaceByName(name string) (*Interface, error) { if name == "" { - return nil, errInvalidInterfaceName + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceName} } ift, err := interfaceTable(0) if err != nil { - return nil, err + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} } for _, ifi := range ift { if name == ifi.Name { return &ifi, nil } } - return nil, errNoSuchInterface + return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errNoSuchInterface} } diff --git a/libgo/go/net/interface_bsd.go b/libgo/go/net/interface_bsd.go index 16775579d05..208f37f9fd3 100644 --- a/libgo/go/net/interface_bsd.go +++ b/libgo/go/net/interface_bsd.go @@ -18,11 +18,11 @@ import ( func interfaceTable(ifindex int) ([]Interface, error) { tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST, ifindex) if err != nil { - return nil, os.NewSyscallError("route rib", err) + return nil, os.NewSyscallError("routerib", err) } msgs, err := syscall.ParseRoutingMessage(tab) if err != nil { - return nil, os.NewSyscallError("route message", err) + return nil, os.NewSyscallError("parseroutingmessage", err) } return parseInterfaceTable(ifindex, msgs) } @@ -51,27 +51,25 @@ loop: func newLink(m *syscall.InterfaceMessage) (*Interface, error) { sas, err := syscall.ParseRoutingSockaddr(m) if err != nil { - return nil, os.NewSyscallError("route sockaddr", err) + return nil, os.NewSyscallError("parseroutingsockaddr", err) } ifi := &Interface{Index: int(m.Header.Index), Flags: linkFlags(m.Header.Flags)} - for _, sa := range sas { - switch sa := sa.(type) { - case *syscall.SockaddrDatalink: - // NOTE: SockaddrDatalink.Data is minimum work area, - // can be larger. - m.Data = m.Data[unsafe.Offsetof(sa.Data):] - var name [syscall.IFNAMSIZ]byte - for i := 0; i < int(sa.Nlen); i++ { - name[i] = byte(m.Data[i]) - } - ifi.Name = string(name[:sa.Nlen]) - ifi.MTU = int(m.Header.Data.Mtu) - addr := make([]byte, sa.Alen) - for i := 0; i < int(sa.Alen); i++ { - addr[i] = byte(m.Data[int(sa.Nlen)+i]) - } - ifi.HardwareAddr = addr[:sa.Alen] + sa, _ := sas[syscall.RTAX_IFP].(*syscall.SockaddrDatalink) + if sa != nil { + // NOTE: SockaddrDatalink.Data is minimum work area, + // can be larger. + m.Data = m.Data[unsafe.Offsetof(sa.Data):] + var name [syscall.IFNAMSIZ]byte + for i := 0; i < int(sa.Nlen); i++ { + name[i] = byte(m.Data[i]) } + ifi.Name = string(name[:sa.Nlen]) + ifi.MTU = int(m.Header.Data.Mtu) + addr := make([]byte, sa.Alen) + for i := 0; i < int(sa.Alen); i++ { + addr[i] = byte(m.Data[int(sa.Nlen)+i]) + } + ifi.HardwareAddr = addr[:sa.Alen] } return ifi, nil } @@ -106,11 +104,11 @@ func interfaceAddrTable(ifi *Interface) ([]Addr, error) { } tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST, index) if err != nil { - return nil, os.NewSyscallError("route rib", err) + return nil, os.NewSyscallError("routerib", err) } msgs, err := syscall.ParseRoutingMessage(tab) if err != nil { - return nil, os.NewSyscallError("route message", err) + return nil, os.NewSyscallError("parseroutingmessage", err) } var ift []Interface if index == 0 { @@ -144,39 +142,34 @@ func interfaceAddrTable(ifi *Interface) ([]Addr, error) { return ifat, nil } -func newAddr(ifi *Interface, m *syscall.InterfaceAddrMessage) (Addr, error) { +func newAddr(ifi *Interface, m *syscall.InterfaceAddrMessage) (*IPNet, error) { sas, err := syscall.ParseRoutingSockaddr(m) if err != nil { - return nil, os.NewSyscallError("route sockaddr", err) + return nil, os.NewSyscallError("parseroutingsockaddr", err) } ifa := &IPNet{} - for i, sa := range sas { - switch sa := sa.(type) { - case *syscall.SockaddrInet4: - switch i { - case 0: - ifa.Mask = IPv4Mask(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]) - case 1: - ifa.IP = IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]) - } - case *syscall.SockaddrInet6: - switch i { - case 0: - ifa.Mask = make(IPMask, IPv6len) - copy(ifa.Mask, sa.Addr[:]) - case 1: - ifa.IP = make(IP, IPv6len) - copy(ifa.IP, sa.Addr[:]) - // NOTE: KAME based IPv6 protcol stack usually embeds - // the interface index in the interface-local or link- - // local address as the kernel-internal form. - if ifa.IP.IsLinkLocalUnicast() { - ifa.IP[2], ifa.IP[3] = 0, 0 - } - } - default: // Sockaddrs contain syscall.SockaddrDatalink on NetBSD - return nil, nil + switch sa := sas[syscall.RTAX_NETMASK].(type) { + case *syscall.SockaddrInet4: + ifa.Mask = IPv4Mask(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]) + case *syscall.SockaddrInet6: + ifa.Mask = make(IPMask, IPv6len) + copy(ifa.Mask, sa.Addr[:]) + } + switch sa := sas[syscall.RTAX_IFA].(type) { + case *syscall.SockaddrInet4: + ifa.IP = IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]) + case *syscall.SockaddrInet6: + ifa.IP = make(IP, IPv6len) + copy(ifa.IP, sa.Addr[:]) + // NOTE: KAME based IPv6 protcol stack usually embeds + // the interface index in the interface-local or + // link-local address as the kernel-internal form. + if ifa.IP.IsLinkLocalUnicast() { + ifa.IP[2], ifa.IP[3] = 0, 0 } } + if ifa.IP == nil || ifa.Mask == nil { + return nil, nil // Sockaddrs contain syscall.SockaddrDatalink on NetBSD + } return ifa, nil } diff --git a/libgo/go/net/interface_darwin.go b/libgo/go/net/interface_darwin.go index ad0937db047..b7a333849d1 100644 --- a/libgo/go/net/interface_darwin.go +++ b/libgo/go/net/interface_darwin.go @@ -14,11 +14,11 @@ import ( func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { tab, err := syscall.RouteRIB(syscall.NET_RT_IFLIST2, ifi.Index) if err != nil { - return nil, os.NewSyscallError("route rib", err) + return nil, os.NewSyscallError("routerib", err) } msgs, err := syscall.ParseRoutingMessage(tab) if err != nil { - return nil, os.NewSyscallError("route message", err) + return nil, os.NewSyscallError("parseroutingmessage", err) } var ifmat []Addr for _, m := range msgs { @@ -29,35 +29,34 @@ func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { if err != nil { return nil, err } - ifmat = append(ifmat, ifma...) + if ifma != nil { + ifmat = append(ifmat, ifma) + } } } } return ifmat, nil } -func newMulticastAddr(ifi *Interface, m *syscall.InterfaceMulticastAddrMessage) ([]Addr, error) { +func newMulticastAddr(ifi *Interface, m *syscall.InterfaceMulticastAddrMessage) (*IPAddr, error) { sas, err := syscall.ParseRoutingSockaddr(m) if err != nil { - return nil, os.NewSyscallError("route sockaddr", err) + return nil, os.NewSyscallError("parseroutingsockaddr", err) } - var ifmat []Addr - for _, sa := range sas { - switch sa := sa.(type) { - case *syscall.SockaddrInet4: - ifma := &IPAddr{IP: IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])} - ifmat = append(ifmat, ifma.toAddr()) - case *syscall.SockaddrInet6: - ifma := &IPAddr{IP: make(IP, IPv6len)} - copy(ifma.IP, sa.Addr[:]) - // NOTE: KAME based IPv6 protocol stack usually embeds - // the interface index in the interface-local or link- - // local address as the kernel-internal form. - if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() { - ifma.IP[2], ifma.IP[3] = 0, 0 - } - ifmat = append(ifmat, ifma.toAddr()) + switch sa := sas[syscall.RTAX_IFA].(type) { + case *syscall.SockaddrInet4: + return &IPAddr{IP: IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])}, nil + case *syscall.SockaddrInet6: + ifma := IPAddr{IP: make(IP, IPv6len)} + copy(ifma.IP, sa.Addr[:]) + // NOTE: KAME based IPv6 protcol stack usually embeds + // the interface index in the interface-local or + // link-local address as the kernel-internal form. + if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() { + ifma.IP[2], ifma.IP[3] = 0, 0 } + return &ifma, nil + default: + return nil, nil } - return ifmat, nil } diff --git a/libgo/go/net/interface_freebsd.go b/libgo/go/net/interface_freebsd.go index 5df767910e4..c42d90b7403 100644 --- a/libgo/go/net/interface_freebsd.go +++ b/libgo/go/net/interface_freebsd.go @@ -14,11 +14,11 @@ import ( func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { tab, err := syscall.RouteRIB(syscall.NET_RT_IFMALIST, ifi.Index) if err != nil { - return nil, os.NewSyscallError("route rib", err) + return nil, os.NewSyscallError("routerib", err) } msgs, err := syscall.ParseRoutingMessage(tab) if err != nil { - return nil, os.NewSyscallError("route message", err) + return nil, os.NewSyscallError("parseroutingmessage", err) } var ifmat []Addr for _, m := range msgs { @@ -29,35 +29,34 @@ func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { if err != nil { return nil, err } - ifmat = append(ifmat, ifma...) + if ifma != nil { + ifmat = append(ifmat, ifma) + } } } } return ifmat, nil } -func newMulticastAddr(ifi *Interface, m *syscall.InterfaceMulticastAddrMessage) ([]Addr, error) { +func newMulticastAddr(ifi *Interface, m *syscall.InterfaceMulticastAddrMessage) (*IPAddr, error) { sas, err := syscall.ParseRoutingSockaddr(m) if err != nil { - return nil, os.NewSyscallError("route sockaddr", err) + return nil, os.NewSyscallError("parseroutingsockaddr", err) } - var ifmat []Addr - for _, sa := range sas { - switch sa := sa.(type) { - case *syscall.SockaddrInet4: - ifma := &IPAddr{IP: IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])} - ifmat = append(ifmat, ifma.toAddr()) - case *syscall.SockaddrInet6: - ifma := &IPAddr{IP: make(IP, IPv6len)} - copy(ifma.IP, sa.Addr[:]) - // NOTE: KAME based IPv6 protocol stack usually embeds - // the interface index in the interface-local or link- - // local address as the kernel-internal form. - if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() { - ifma.IP[2], ifma.IP[3] = 0, 0 - } - ifmat = append(ifmat, ifma.toAddr()) + switch sa := sas[syscall.RTAX_IFA].(type) { + case *syscall.SockaddrInet4: + return &IPAddr{IP: IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])}, nil + case *syscall.SockaddrInet6: + ifma := IPAddr{IP: make(IP, IPv6len)} + copy(ifma.IP, sa.Addr[:]) + // NOTE: KAME based IPv6 protcol stack usually embeds + // the interface index in the interface-local or + // link-local address as the kernel-internal form. + if ifma.IP.IsInterfaceLocalMulticast() || ifma.IP.IsLinkLocalMulticast() { + ifma.IP[2], ifma.IP[3] = 0, 0 } + return &ifma, nil + default: + return nil, nil } - return ifmat, nil } diff --git a/libgo/go/net/interface_linux.go b/libgo/go/net/interface_linux.go index 1115d0fc40b..ef2042920ed 100644 --- a/libgo/go/net/interface_linux.go +++ b/libgo/go/net/interface_linux.go @@ -16,11 +16,11 @@ import ( func interfaceTable(ifindex int) ([]Interface, error) { tab, err := syscall.NetlinkRIB(syscall.RTM_GETLINK, syscall.AF_UNSPEC) if err != nil { - return nil, os.NewSyscallError("netlink rib", err) + return nil, os.NewSyscallError("netlinkrib", err) } msgs, err := syscall.ParseNetlinkMessage(tab) if err != nil { - return nil, os.NewSyscallError("netlink message", err) + return nil, os.NewSyscallError("parsenetlinkmessage", err) } var ift []Interface loop: @@ -33,7 +33,7 @@ loop: if ifindex == 0 || ifindex == int(ifim.Index) { attrs, err := syscall.ParseNetlinkRouteAttr(&m) if err != nil { - return nil, os.NewSyscallError("netlink routeattr", err) + return nil, os.NewSyscallError("parsenetlinkrouteattr", err) } ift = append(ift, *newLink(ifim, attrs)) if ifindex == int(ifim.Index) { @@ -120,11 +120,11 @@ func linkFlags(rawFlags uint32) Flags { func interfaceAddrTable(ifi *Interface) ([]Addr, error) { tab, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC) if err != nil { - return nil, os.NewSyscallError("netlink rib", err) + return nil, os.NewSyscallError("netlinkrib", err) } msgs, err := syscall.ParseNetlinkMessage(tab) if err != nil { - return nil, os.NewSyscallError("netlink message", err) + return nil, os.NewSyscallError("parsenetlinkmessage", err) } var ift []Interface if ifi == nil { @@ -160,7 +160,7 @@ loop: } attrs, err := syscall.ParseNetlinkRouteAttr(&m) if err != nil { - return nil, os.NewSyscallError("netlink routeattr", err) + return nil, os.NewSyscallError("parsenetlinkrouteattr", err) } ifa := newAddr(ifi, ifam, attrs) if ifa != nil { @@ -176,17 +176,15 @@ func newAddr(ifi *Interface, ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRou var ipPointToPoint bool // Seems like we need to make sure whether the IP interface // stack consists of IP point-to-point numbered or unnumbered - // addressing over point-to-point link encapsulation. - if ifi.Flags&FlagPointToPoint != 0 { - for _, a := range attrs { - if a.Attr.Type == syscall.IFA_LOCAL { - ipPointToPoint = true - break - } + // addressing. + for _, a := range attrs { + if a.Attr.Type == syscall.IFA_LOCAL { + ipPointToPoint = true + break } } for _, a := range attrs { - if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS || !ipPointToPoint && a.Attr.Type == syscall.IFA_LOCAL { + if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS { continue } switch ifam.Family { @@ -238,8 +236,8 @@ func parseProcNetIGMP(path string, ifi *Interface) []Addr { b[i/2], _ = xtoi2(f[0][i:i+2], 0) } i := *(*uint32)(unsafe.Pointer(&b[:4][0])) - ifma := IPAddr{IP: IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i))} - ifmat = append(ifmat, ifma.toAddr()) + ifma := &IPAddr{IP: IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i))} + ifmat = append(ifmat, ifma) } } } @@ -263,8 +261,8 @@ func parseProcNetIGMP6(path string, ifi *Interface) []Addr { for i := 0; i+1 < len(f[2]); i += 2 { b[i/2], _ = xtoi2(f[2][i:i+2], 0) } - ifma := IPAddr{IP: IP{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}} - ifmat = append(ifmat, ifma.toAddr()) + ifma := &IPAddr{IP: IP{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}} + ifmat = append(ifmat, ifma) } } return ifmat diff --git a/libgo/go/net/interface_test.go b/libgo/go/net/interface_test.go index efabb5f3c25..567d18de448 100644 --- a/libgo/go/net/interface_test.go +++ b/libgo/go/net/interface_test.go @@ -6,6 +6,7 @@ package net import ( "reflect" + "runtime" "testing" ) @@ -37,12 +38,7 @@ func ipv6LinkLocalUnicastAddr(ifi *Interface) string { return "" } for _, ifa := range ifat { - switch ifa := ifa.(type) { - case *IPAddr: - if ifa.IP.To4() == nil && ifa.IP.IsLinkLocalUnicast() { - return ifa.IP.String() - } - case *IPNet: + if ifa, ok := ifa.(*IPNet); ok { if ifa.IP.To4() == nil && ifa.IP.IsLinkLocalUnicast() { return ifa.IP.String() } @@ -51,161 +47,259 @@ func ipv6LinkLocalUnicastAddr(ifi *Interface) string { return "" } +type routeStats struct { + loop int // # of active loopback interfaces + other int // # of active other interfaces + + uni4, uni6 int // # of active connected unicast, anycast routes + multi4, multi6 int // # of active connected multicast route clones +} + func TestInterfaces(t *testing.T) { ift, err := Interfaces() if err != nil { - t.Fatalf("Interfaces failed: %v", err) + t.Fatal(err) } - t.Logf("table: len/cap = %v/%v", len(ift), cap(ift)) - + var stats routeStats for _, ifi := range ift { ifxi, err := InterfaceByIndex(ifi.Index) if err != nil { - t.Fatalf("InterfaceByIndex(%v) failed: %v", ifi.Index, err) + t.Fatal(err) } if !reflect.DeepEqual(ifxi, &ifi) { - t.Fatalf("InterfaceByIndex(%v) = %v, want %v", ifi.Index, ifxi, ifi) + t.Errorf("got %v; want %v", ifxi, ifi) } ifxn, err := InterfaceByName(ifi.Name) if err != nil { - t.Fatalf("InterfaceByName(%q) failed: %v", ifi.Name, err) + t.Fatal(err) } if !reflect.DeepEqual(ifxn, &ifi) { - t.Fatalf("InterfaceByName(%q) = %v, want %v", ifi.Name, ifxn, ifi) + t.Errorf("got %v; want %v", ifxn, ifi) } t.Logf("%q: flags %q, ifindex %v, mtu %v", ifi.Name, ifi.Flags.String(), ifi.Index, ifi.MTU) - t.Logf("\thardware address %q", ifi.HardwareAddr.String()) - testInterfaceAddrs(t, &ifi) - testInterfaceMulticastAddrs(t, &ifi) + t.Logf("hardware address %q", ifi.HardwareAddr.String()) + if ifi.Flags&FlagUp != 0 { + if ifi.Flags&FlagLoopback != 0 { + stats.loop++ + } else { + stats.other++ + } + } + n4, n6 := testInterfaceAddrs(t, &ifi) + stats.uni4 += n4 + stats.uni6 += n6 + n4, n6 = testInterfaceMulticastAddrs(t, &ifi) + stats.multi4 += n4 + stats.multi6 += n6 + } + switch runtime.GOOS { + case "nacl", "plan9", "solaris": + default: + // Test the existence of connected unicast routes for + // IPv4. + if supportsIPv4 && stats.loop+stats.other > 0 && stats.uni4 == 0 { + t.Errorf("num IPv4 unicast routes = 0; want >0; summary: %+v", stats) + } + // Test the existence of connected unicast routes for + // IPv6. We can assume the existence of ::1/128 when + // at least one looopback interface is installed. + if supportsIPv6 && stats.loop > 0 && stats.uni6 == 0 { + t.Errorf("num IPv6 unicast routes = 0; want >0; summary: %+v", stats) + } + } + switch runtime.GOOS { + case "dragonfly", "nacl", "netbsd", "openbsd", "plan9", "solaris": + default: + // Test the existence of connected multicast route + // clones for IPv4. Unlike IPv6, IPv4 multicast + // capability is not a mandatory feature, and so this + // test is disabled. + //if supportsIPv4 && stats.loop > 0 && stats.uni4 > 1 && stats.multi4 == 0 { + // t.Errorf("num IPv4 multicast route clones = 0; want >0; summary: %+v", stats) + //} + // Test the existence of connected multicast route + // clones for IPv6. Some platform never uses loopback + // interface as the nexthop for multicast routing. + // We can assume the existence of connected multicast + // route clones when at least two connected unicast + // routes, ::1/128 and other, are installed. + if supportsIPv6 && stats.loop > 0 && stats.uni6 > 1 && stats.multi6 == 0 { + t.Errorf("num IPv6 multicast route clones = 0; want >0; summary: %+v", stats) + } } } func TestInterfaceAddrs(t *testing.T) { + ift, err := Interfaces() + if err != nil { + t.Fatal(err) + } + var stats routeStats + for _, ifi := range ift { + if ifi.Flags&FlagUp != 0 { + if ifi.Flags&FlagLoopback != 0 { + stats.loop++ + } else { + stats.other++ + } + } + } ifat, err := InterfaceAddrs() if err != nil { - t.Fatalf("InterfaceAddrs failed: %v", err) + t.Fatal(err) + } + stats.uni4, stats.uni6 = testAddrs(t, ifat) + // Test the existence of connected unicast routes for IPv4. + if supportsIPv4 && stats.loop+stats.other > 0 && stats.uni4 == 0 { + t.Errorf("num IPv4 unicast routes = 0; want >0; summary: %+v", stats) + } + // Test the existence of connected unicast routes for IPv6. + // We can assume the existence of ::1/128 when at least one + // looopback interface is installed. + if supportsIPv6 && stats.loop > 0 && stats.uni6 == 0 { + t.Errorf("num IPv6 unicast routes = 0; want >0; summary: %+v", stats) } - t.Logf("table: len/cap = %v/%v", len(ifat), cap(ifat)) - testAddrs(t, ifat) } -func testInterfaceAddrs(t *testing.T, ifi *Interface) { +func testInterfaceAddrs(t *testing.T, ifi *Interface) (naf4, naf6 int) { ifat, err := ifi.Addrs() if err != nil { - t.Fatalf("Interface.Addrs failed: %v", err) + t.Fatal(err) } - testAddrs(t, ifat) + return testAddrs(t, ifat) } -func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) { +func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) (nmaf4, nmaf6 int) { ifmat, err := ifi.MulticastAddrs() if err != nil { - t.Fatalf("Interface.MulticastAddrs failed: %v", err) + t.Fatal(err) } - testMulticastAddrs(t, ifmat) + return testMulticastAddrs(t, ifmat) } -func testAddrs(t *testing.T, ifat []Addr) { +func testAddrs(t *testing.T, ifat []Addr) (naf4, naf6 int) { for _, ifa := range ifat { switch ifa := ifa.(type) { - case *IPAddr: - if ifa == nil || ifa.IP == nil { - t.Errorf("\tunexpected value: %v, %v", ifa, ifa.IP) - } else { - t.Logf("\tinterface address %q", ifa.String()) - } case *IPNet: - if ifa == nil || ifa.IP == nil || ifa.Mask == nil { - t.Errorf("\tunexpected value: %v, %v, %v", ifa, ifa.IP, ifa.Mask) - } else { - _, prefixLen := ifa.Mask.Size() - if ifa.IP.To4() != nil && prefixLen != 8*IPv4len || ifa.IP.To16() != nil && ifa.IP.To4() == nil && prefixLen != 8*IPv6len { - t.Errorf("\tunexpected value: %v, %v, %v, %v", ifa, ifa.IP, ifa.Mask, prefixLen) - } else { - t.Logf("\tinterface address %q", ifa.String()) + if ifa == nil || ifa.IP == nil || ifa.IP.IsUnspecified() || ifa.IP.IsMulticast() || ifa.Mask == nil { + t.Errorf("unexpected value: %#v", ifa) + continue + } + prefixLen, maxPrefixLen := ifa.Mask.Size() + if ifa.IP.To4() != nil { + if 0 >= prefixLen || prefixLen > 8*IPv4len || maxPrefixLen != 8*IPv4len { + t.Errorf("unexpected prefix length: %v/%v", prefixLen, maxPrefixLen) + continue + } + naf4++ + } else if ifa.IP.To16() != nil { + if 0 >= prefixLen || prefixLen > 8*IPv6len || maxPrefixLen != 8*IPv6len { + t.Errorf("unexpected prefix length: %v/%v", prefixLen, maxPrefixLen) + continue } + naf6++ } + t.Logf("interface address %q", ifa.String()) default: - t.Errorf("\tunexpected type: %T", ifa) + t.Errorf("unexpected type: %T", ifa) } } + return } -func testMulticastAddrs(t *testing.T, ifmat []Addr) { +func testMulticastAddrs(t *testing.T, ifmat []Addr) (nmaf4, nmaf6 int) { for _, ifma := range ifmat { switch ifma := ifma.(type) { case *IPAddr: - if ifma == nil { - t.Errorf("\tunexpected value: %v", ifma) - } else { - t.Logf("\tjoined group address %q", ifma.String()) + if ifma == nil || ifma.IP == nil || ifma.IP.IsUnspecified() || !ifma.IP.IsMulticast() { + t.Errorf("unexpected value: %#v", ifma) + continue } + if ifma.IP.To4() != nil { + nmaf4++ + } else if ifma.IP.To16() != nil { + nmaf6++ + } + t.Logf("joined group address %q", ifma.String()) default: - t.Errorf("\tunexpected type: %T", ifma) + t.Errorf("unexpected type: %T", ifma) } } + return } func BenchmarkInterfaces(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { if _, err := Interfaces(); err != nil { - b.Fatalf("Interfaces failed: %v", err) + b.Fatal(err) } } } func BenchmarkInterfaceByIndex(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + ifi := loopbackInterface() if ifi == nil { b.Skip("loopback interface not found") } for i := 0; i < b.N; i++ { if _, err := InterfaceByIndex(ifi.Index); err != nil { - b.Fatalf("InterfaceByIndex failed: %v", err) + b.Fatal(err) } } } func BenchmarkInterfaceByName(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + ifi := loopbackInterface() if ifi == nil { b.Skip("loopback interface not found") } for i := 0; i < b.N; i++ { if _, err := InterfaceByName(ifi.Name); err != nil { - b.Fatalf("InterfaceByName failed: %v", err) + b.Fatal(err) } } } func BenchmarkInterfaceAddrs(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { if _, err := InterfaceAddrs(); err != nil { - b.Fatalf("InterfaceAddrs failed: %v", err) + b.Fatal(err) } } } func BenchmarkInterfacesAndAddrs(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + ifi := loopbackInterface() if ifi == nil { b.Skip("loopback interface not found") } for i := 0; i < b.N; i++ { if _, err := ifi.Addrs(); err != nil { - b.Fatalf("Interface.Addrs failed: %v", err) + b.Fatal(err) } } } func BenchmarkInterfacesAndMulticastAddrs(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + ifi := loopbackInterface() if ifi == nil { b.Skip("loopback interface not found") } for i := 0; i < b.N; i++ { if _, err := ifi.MulticastAddrs(); err != nil { - b.Fatalf("Interface.MulticastAddrs failed: %v", err) + b.Fatal(err) } } } diff --git a/libgo/go/net/interface_windows.go b/libgo/go/net/interface_windows.go index 0759dc255d4..e25c1ed560b 100644 --- a/libgo/go/net/interface_windows.go +++ b/libgo/go/net/interface_windows.go @@ -5,123 +5,139 @@ package net import ( + "internal/syscall/windows" "os" "syscall" "unsafe" ) -func bytePtrToString(p *uint8) string { - a := (*[10000]uint8)(unsafe.Pointer(p)) - i := 0 - for a[i] != 0 { - i++ - } - return string(a[:i]) -} +func getAdapters() (*windows.IpAdapterAddresses, error) { + block := uint32(unsafe.Sizeof(windows.IpAdapterAddresses{})) -func getAdapterList() (*syscall.IpAdapterInfo, error) { - b := make([]byte, 1000) - l := uint32(len(b)) - a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) - // TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that - // contains IPv4 address list only. We should use another API - // for fetching IPv6 stuff from the kernel. - err := syscall.GetAdaptersInfo(a, &l) - if err == syscall.ERROR_BUFFER_OVERFLOW { - b = make([]byte, l) - a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) - err = syscall.GetAdaptersInfo(a, &l) - } - if err != nil { - return nil, os.NewSyscallError("GetAdaptersInfo", err) + // pre-allocate a 15KB working buffer pointed to by the AdapterAddresses + // parameter. + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx + size := uint32(15000) + + var addrs []windows.IpAdapterAddresses + for { + addrs = make([]windows.IpAdapterAddresses, size/block+1) + err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, &addrs[0], &size) + if err == nil { + break + } + if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } } - return a, nil + return &addrs[0], nil } -func getInterfaceList() ([]syscall.InterfaceInfo, error) { +func getInterfaceInfos() ([]syscall.InterfaceInfo, error) { s, err := sysSocket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) if err != nil { - return nil, os.NewSyscallError("Socket", err) + return nil, err } - defer syscall.Closesocket(s) + defer closeFunc(s) - ii := [20]syscall.InterfaceInfo{} + iia := [20]syscall.InterfaceInfo{} ret := uint32(0) - size := uint32(unsafe.Sizeof(ii)) - err = syscall.WSAIoctl(s, syscall.SIO_GET_INTERFACE_LIST, nil, 0, (*byte)(unsafe.Pointer(&ii[0])), size, &ret, nil, 0) + size := uint32(unsafe.Sizeof(iia)) + err = syscall.WSAIoctl(s, syscall.SIO_GET_INTERFACE_LIST, nil, 0, (*byte)(unsafe.Pointer(&iia[0])), size, &ret, nil, 0) if err != nil { - return nil, os.NewSyscallError("WSAIoctl", err) + return nil, os.NewSyscallError("wsaioctl", err) } - c := ret / uint32(unsafe.Sizeof(ii[0])) - return ii[:c-1], nil + iilen := ret / uint32(unsafe.Sizeof(iia[0])) + return iia[:iilen-1], nil +} + +func bytesEqualIP(a []byte, b []int8) bool { + for i := 0; i < len(a); i++ { + if a[i] != byte(b[i]) { + return false + } + } + return true +} + +func findInterfaceInfo(iis []syscall.InterfaceInfo, paddr *windows.IpAdapterAddresses) *syscall.InterfaceInfo { + for _, ii := range iis { + iaddr := (*syscall.RawSockaddr)(unsafe.Pointer(&ii.Address)) + puni := paddr.FirstUnicastAddress + for ; puni != nil; puni = puni.Next { + if iaddr.Family == puni.Address.Sockaddr.Addr.Family { + switch iaddr.Family { + case syscall.AF_INET: + a := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&ii.Address)).Addr + if bytesEqualIP(a[:], puni.Address.Sockaddr.Addr.Data[2:]) { + return &ii + } + case syscall.AF_INET6: + a := (*syscall.RawSockaddrInet6)(unsafe.Pointer(&ii.Address)).Addr + if bytesEqualIP(a[:], puni.Address.Sockaddr.Addr.Data[2:]) { + return &ii + } + default: + continue + } + } + } + } + return nil } // If the ifindex is zero, interfaceTable returns mappings of all // network interfaces. Otherwise it returns a mapping of a specific // interface. func interfaceTable(ifindex int) ([]Interface, error) { - ai, err := getAdapterList() + paddr, err := getAdapters() if err != nil { return nil, err } - ii, err := getInterfaceList() + iis, err := getInterfaceInfos() if err != nil { return nil, err } var ift []Interface - for ; ai != nil; ai = ai.Next { - index := ai.Index + for ; paddr != nil; paddr = paddr.Next { + index := paddr.IfIndex + if paddr.Ipv6IfIndex != 0 { + index = paddr.Ipv6IfIndex + } if ifindex == 0 || ifindex == int(index) { + ii := findInterfaceInfo(iis, paddr) + if ii == nil { + continue + } var flags Flags - - row := syscall.MibIfRow{Index: index} - e := syscall.GetIfEntry(&row) - if e != nil { - return nil, os.NewSyscallError("GetIfEntry", e) + if paddr.Flags&windows.IfOperStatusUp != 0 { + flags |= FlagUp } - - for _, ii := range ii { - ip := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&ii.Address)).Addr - ipv4 := IPv4(ip[0], ip[1], ip[2], ip[3]) - ipl := &ai.IpAddressList - for ipl != nil { - ips := bytePtrToString(&ipl.IpAddress.String[0]) - if ipv4.Equal(parseIPv4(ips)) { - break - } - ipl = ipl.Next - } - if ipl == nil { - continue - } - if ii.Flags&syscall.IFF_UP != 0 { - flags |= FlagUp - } - if ii.Flags&syscall.IFF_LOOPBACK != 0 { - flags |= FlagLoopback - } - if ii.Flags&syscall.IFF_BROADCAST != 0 { - flags |= FlagBroadcast - } - if ii.Flags&syscall.IFF_POINTTOPOINT != 0 { - flags |= FlagPointToPoint - } - if ii.Flags&syscall.IFF_MULTICAST != 0 { - flags |= FlagMulticast - } + if paddr.IfType&windows.IF_TYPE_SOFTWARE_LOOPBACK != 0 { + flags |= FlagLoopback + } + if ii.Flags&syscall.IFF_BROADCAST != 0 { + flags |= FlagBroadcast + } + if ii.Flags&syscall.IFF_POINTTOPOINT != 0 { + flags |= FlagPointToPoint + } + if ii.Flags&syscall.IFF_MULTICAST != 0 { + flags |= FlagMulticast } - - name := bytePtrToString(&ai.AdapterName[0]) - ifi := Interface{ Index: int(index), - MTU: int(row.Mtu), - Name: name, - HardwareAddr: HardwareAddr(row.PhysAddr[:row.PhysAddrLen]), - Flags: flags} + MTU: int(paddr.Mtu), + Name: syscall.UTF16ToString((*(*[10000]uint16)(unsafe.Pointer(paddr.FriendlyName)))[:]), + HardwareAddr: HardwareAddr(paddr.PhysicalAddress[:]), + Flags: flags, + } ift = append(ift, ifi) + if ifindex == int(ifi.Index) { + break + } } } return ift, nil @@ -131,28 +147,86 @@ func interfaceTable(ifindex int) ([]Interface, error) { // network interfaces. Otherwise it returns addresses for a specific // interface. func interfaceAddrTable(ifi *Interface) ([]Addr, error) { - ai, err := getAdapterList() + paddr, err := getAdapters() if err != nil { return nil, err } var ifat []Addr - for ; ai != nil; ai = ai.Next { - index := ai.Index + for ; paddr != nil; paddr = paddr.Next { + index := paddr.IfIndex + if paddr.Ipv6IfIndex != 0 { + index = paddr.Ipv6IfIndex + } if ifi == nil || ifi.Index == int(index) { - ipl := &ai.IpAddressList - for ; ipl != nil; ipl = ipl.Next { - ifa := IPAddr{IP: parseIPv4(bytePtrToString(&ipl.IpAddress.String[0]))} - ifat = append(ifat, ifa.toAddr()) + puni := paddr.FirstUnicastAddress + for ; puni != nil; puni = puni.Next { + if sa, err := puni.Address.Sockaddr.Sockaddr(); err == nil { + switch sav := sa.(type) { + case *syscall.SockaddrInet4: + ifa := &IPNet{IP: make(IP, IPv4len), Mask: CIDRMask(int(puni.Address.SockaddrLength), 8*IPv4len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + case *syscall.SockaddrInet6: + ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(puni.Address.SockaddrLength), 8*IPv6len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + } + } + } + pany := paddr.FirstAnycastAddress + for ; pany != nil; pany = pany.Next { + if sa, err := pany.Address.Sockaddr.Sockaddr(); err == nil { + switch sav := sa.(type) { + case *syscall.SockaddrInet4: + ifa := &IPNet{IP: make(IP, IPv4len), Mask: CIDRMask(int(pany.Address.SockaddrLength), 8*IPv4len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + case *syscall.SockaddrInet6: + ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(pany.Address.SockaddrLength), 8*IPv6len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + } + } } } } + return ifat, nil } // interfaceMulticastAddrTable returns addresses for a specific // interface. func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { - // TODO(mikio): Implement this like other platforms. - return nil, nil + paddr, err := getAdapters() + if err != nil { + return nil, err + } + + var ifat []Addr + for ; paddr != nil; paddr = paddr.Next { + index := paddr.IfIndex + if paddr.Ipv6IfIndex != 0 { + index = paddr.Ipv6IfIndex + } + if ifi == nil || ifi.Index == int(index) { + pmul := paddr.FirstMulticastAddress + for ; pmul != nil; pmul = pmul.Next { + if sa, err := pmul.Address.Sockaddr.Sockaddr(); err == nil { + switch sav := sa.(type) { + case *syscall.SockaddrInet4: + ifa := &IPAddr{IP: make(IP, IPv4len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + case *syscall.SockaddrInet6: + ifa := &IPAddr{IP: make(IP, IPv6len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + } + } + } + } + } + + return ifat, nil } diff --git a/libgo/go/net/internal/socktest/main_test.go b/libgo/go/net/internal/socktest/main_test.go new file mode 100644 index 00000000000..60e581f463c --- /dev/null +++ b/libgo/go/net/internal/socktest/main_test.go @@ -0,0 +1,56 @@ +// Copyright 2015 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 !plan9 + +package socktest_test + +import ( + "net/internal/socktest" + "os" + "sync" + "syscall" + "testing" +) + +var sw socktest.Switch + +func TestMain(m *testing.M) { + installTestHooks() + + st := m.Run() + + for s := range sw.Sockets() { + closeFunc(s) + } + uninstallTestHooks() + os.Exit(st) +} + +func TestSwitch(t *testing.T) { + const N = 10 + var wg sync.WaitGroup + wg.Add(N) + for i := 0; i < N; i++ { + go func() { + defer wg.Done() + for _, family := range []int{syscall.AF_INET, syscall.AF_INET6} { + socketFunc(family, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) + } + }() + } + wg.Wait() +} + +func TestSocket(t *testing.T) { + for _, f := range []socktest.Filter{ + func(st *socktest.Status) (socktest.AfterFilter, error) { return nil, nil }, + nil, + } { + sw.Set(socktest.FilterSocket, f) + for _, family := range []int{syscall.AF_INET, syscall.AF_INET6} { + socketFunc(family, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) + } + } +} diff --git a/libgo/go/net/internal/socktest/main_unix_test.go b/libgo/go/net/internal/socktest/main_unix_test.go new file mode 100644 index 00000000000..b8eebc2aa42 --- /dev/null +++ b/libgo/go/net/internal/socktest/main_unix_test.go @@ -0,0 +1,24 @@ +// Copyright 2015 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 !plan9,!windows + +package socktest_test + +import "syscall" + +var ( + socketFunc func(int, int, int) (int, error) + closeFunc func(int) error +) + +func installTestHooks() { + socketFunc = sw.Socket + closeFunc = sw.Close +} + +func uninstallTestHooks() { + socketFunc = syscall.Socket + closeFunc = syscall.Close +} diff --git a/libgo/go/net/internal/socktest/main_windows_test.go b/libgo/go/net/internal/socktest/main_windows_test.go new file mode 100644 index 00000000000..df1cb97784b --- /dev/null +++ b/libgo/go/net/internal/socktest/main_windows_test.go @@ -0,0 +1,22 @@ +// Copyright 2015 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 socktest_test + +import "syscall" + +var ( + socketFunc func(int, int, int) (syscall.Handle, error) + closeFunc func(syscall.Handle) error +) + +func installTestHooks() { + socketFunc = sw.Socket + closeFunc = sw.Closesocket +} + +func uninstallTestHooks() { + socketFunc = syscall.Socket + closeFunc = syscall.Closesocket +} diff --git a/libgo/go/net/internal/socktest/switch.go b/libgo/go/net/internal/socktest/switch.go new file mode 100644 index 00000000000..4e38c7a85f3 --- /dev/null +++ b/libgo/go/net/internal/socktest/switch.go @@ -0,0 +1,169 @@ +// Copyright 2015 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 socktest provides utilities for socket testing. +package socktest + +import ( + "fmt" + "sync" +) + +// A Switch represents a callpath point switch for socket system +// calls. +type Switch struct { + once sync.Once + + fmu sync.RWMutex + fltab map[FilterType]Filter + + smu sync.RWMutex + sotab Sockets + stats stats +} + +func (sw *Switch) init() { + sw.fltab = make(map[FilterType]Filter) + sw.sotab = make(Sockets) + sw.stats = make(stats) +} + +// Stats returns a list of per-cookie socket statistics. +func (sw *Switch) Stats() []Stat { + var st []Stat + sw.smu.RLock() + for _, s := range sw.stats { + ns := *s + st = append(st, ns) + } + sw.smu.RUnlock() + return st +} + +// Sockets returns mappings of socket descriptor to socket status. +func (sw *Switch) Sockets() Sockets { + sw.smu.RLock() + tab := make(Sockets, len(sw.sotab)) + for i, s := range sw.sotab { + tab[i] = s + } + sw.smu.RUnlock() + return tab +} + +// A Cookie represents a 3-tuple of a socket; address family, socket +// type and protocol number. +type Cookie uint64 + +// Family returns an address family. +func (c Cookie) Family() int { return int(c >> 48) } + +// Type returns a socket type. +func (c Cookie) Type() int { return int(c << 16 >> 32) } + +// Protocol returns a protocol number. +func (c Cookie) Protocol() int { return int(c & 0xff) } + +func cookie(family, sotype, proto int) Cookie { + return Cookie(family)<<48 | Cookie(sotype)&0xffffffff<<16 | Cookie(proto)&0xff +} + +// A Status represents the status of a socket. +type Status struct { + Cookie Cookie + Err error // error status of socket system call + SocketErr error // error status of socket by SO_ERROR +} + +func (so Status) String() string { + return fmt.Sprintf("(%s, %s, %s): syscallerr=%v, socketerr=%v", familyString(so.Cookie.Family()), typeString(so.Cookie.Type()), protocolString(so.Cookie.Protocol()), so.Err, so.SocketErr) +} + +// A Stat represents a per-cookie socket statistics. +type Stat struct { + Family int // address family + Type int // socket type + Protocol int // protocol number + + Opened uint64 // number of sockets opened + Connected uint64 // number of sockets connected + Listened uint64 // number of sockets listened + Accepted uint64 // number of sockets accepted + Closed uint64 // number of sockets closed + + OpenFailed uint64 // number of sockets open failed + ConnectFailed uint64 // number of sockets connect failed + ListenFailed uint64 // number of sockets listen failed + AcceptFailed uint64 // number of sockets accept failed + CloseFailed uint64 // number of sockets close failed +} + +func (st Stat) String() string { + return fmt.Sprintf("(%s, %s, %s): opened=%d, connected=%d, listened=%d, accepted=%d, closed=%d, openfailed=%d, connectfailed=%d, listenfailed=%d, acceptfailed=%d, closefailed=%d", familyString(st.Family), typeString(st.Type), protocolString(st.Protocol), st.Opened, st.Connected, st.Listened, st.Accepted, st.Closed, st.OpenFailed, st.ConnectFailed, st.ListenFailed, st.AcceptFailed, st.CloseFailed) +} + +type stats map[Cookie]*Stat + +func (st stats) getLocked(c Cookie) *Stat { + s, ok := st[c] + if !ok { + s = &Stat{Family: c.Family(), Type: c.Type(), Protocol: c.Protocol()} + st[c] = s + } + return s +} + +// A FilterType represents a filter type. +type FilterType int + +const ( + FilterSocket FilterType = iota // for Socket + FilterConnect // for Connect or ConnectEx + FilterListen // for Listen + FilterAccept // for Accept or Accept4 + FilterGetsockoptInt // for GetsockoptInt + FilterClose // for Close or Closesocket +) + +// A Filter represents a socket system call filter. +// +// It will only be executed before a system call for a socket that has +// an entry in internal table. +// If the filter returns a non-nil error, the execution of system call +// will be canceled and the system call function returns the non-nil +// error. +// It can return a non-nil AfterFilter for filtering after the +// execution of the system call. +type Filter func(*Status) (AfterFilter, error) + +func (f Filter) apply(st *Status) (AfterFilter, error) { + if f == nil { + return nil, nil + } + return f(st) +} + +// An AfterFilter represents a socket system call filter after an +// execution of a system call. +// +// It will only be executed after a system call for a socket that has +// an entry in internal table. +// If the filter returns a non-nil error, the system call function +// returns the non-nil error. +type AfterFilter func(*Status) error + +func (f AfterFilter) apply(st *Status) error { + if f == nil { + return nil + } + return f(st) +} + +// Set deploys the socket system call filter f for the filter type t. +func (sw *Switch) Set(t FilterType, f Filter) { + sw.once.Do(sw.init) + sw.fmu.Lock() + sw.fltab[t] = f + sw.fmu.Unlock() +} diff --git a/libgo/go/net/internal/socktest/switch_posix.go b/libgo/go/net/internal/socktest/switch_posix.go new file mode 100644 index 00000000000..863edef0d35 --- /dev/null +++ b/libgo/go/net/internal/socktest/switch_posix.go @@ -0,0 +1,58 @@ +// Copyright 2015 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 !plan9 + +package socktest + +import ( + "fmt" + "syscall" +) + +func familyString(family int) string { + switch family { + case syscall.AF_INET: + return "inet4" + case syscall.AF_INET6: + return "inet6" + case syscall.AF_UNIX: + return "local" + default: + return fmt.Sprintf("%d", family) + } +} + +func typeString(sotype int) string { + var s string + switch sotype & 0xff { + case syscall.SOCK_STREAM: + s = "stream" + case syscall.SOCK_DGRAM: + s = "datagram" + case syscall.SOCK_RAW: + s = "raw" + case syscall.SOCK_SEQPACKET: + s = "seqpacket" + default: + s = fmt.Sprintf("%d", sotype&0xff) + } + if flags := uint(sotype) & ^uint(0xff); flags != 0 { + s += fmt.Sprintf("|%#x", flags) + } + return s +} + +func protocolString(proto int) string { + switch proto { + case 0: + return "default" + case syscall.IPPROTO_TCP: + return "tcp" + case syscall.IPPROTO_UDP: + return "udp" + default: + return fmt.Sprintf("%d", proto) + } +} diff --git a/libgo/go/net/internal/socktest/switch_stub.go b/libgo/go/net/internal/socktest/switch_stub.go new file mode 100644 index 00000000000..28ce72cb855 --- /dev/null +++ b/libgo/go/net/internal/socktest/switch_stub.go @@ -0,0 +1,16 @@ +// Copyright 2015 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 plan9 + +package socktest + +// Sockets maps a socket descriptor to the status of socket. +type Sockets map[int]Status + +func familyString(family int) string { return "<nil>" } + +func typeString(sotype int) string { return "<nil>" } + +func protocolString(proto int) string { return "<nil>" } diff --git a/libgo/go/net/internal/socktest/switch_unix.go b/libgo/go/net/internal/socktest/switch_unix.go new file mode 100644 index 00000000000..14c0c228a2f --- /dev/null +++ b/libgo/go/net/internal/socktest/switch_unix.go @@ -0,0 +1,29 @@ +// Copyright 2015 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 dragonfly freebsd linux nacl netbsd openbsd solaris + +package socktest + +// Sockets maps a socket descriptor to the status of socket. +type Sockets map[int]Status + +func (sw *Switch) sockso(s int) *Status { + sw.smu.RLock() + defer sw.smu.RUnlock() + so, ok := sw.sotab[s] + if !ok { + return nil + } + return &so +} + +// addLocked returns a new Status without locking. +// sw.smu must be held before call. +func (sw *Switch) addLocked(s, family, sotype, proto int) *Status { + sw.once.Do(sw.init) + so := Status{Cookie: cookie(family, sotype, proto)} + sw.sotab[s] = so + return &so +} diff --git a/libgo/go/net/internal/socktest/switch_windows.go b/libgo/go/net/internal/socktest/switch_windows.go new file mode 100644 index 00000000000..4f1d597a27a --- /dev/null +++ b/libgo/go/net/internal/socktest/switch_windows.go @@ -0,0 +1,29 @@ +// Copyright 2015 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 socktest + +import "syscall" + +// Sockets maps a socket descriptor to the status of socket. +type Sockets map[syscall.Handle]Status + +func (sw *Switch) sockso(s syscall.Handle) *Status { + sw.smu.RLock() + defer sw.smu.RUnlock() + so, ok := sw.sotab[s] + if !ok { + return nil + } + return &so +} + +// addLocked returns a new Status without locking. +// sw.smu must be held before call. +func (sw *Switch) addLocked(s syscall.Handle, family, sotype, proto int) *Status { + sw.once.Do(sw.init) + so := Status{Cookie: cookie(family, sotype, proto)} + sw.sotab[s] = so + return &so +} diff --git a/libgo/go/net/internal/socktest/sys_cloexec.go b/libgo/go/net/internal/socktest/sys_cloexec.go new file mode 100644 index 00000000000..340ff071e7e --- /dev/null +++ b/libgo/go/net/internal/socktest/sys_cloexec.go @@ -0,0 +1,42 @@ +// Copyright 2015 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 freebsd linux + +package socktest + +import "syscall" + +// Accept4 wraps syscall.Accept4. +func (sw *Switch) Accept4(s, flags int) (ns int, sa syscall.Sockaddr, err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Accept4(s, flags) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterAccept] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return -1, nil, err + } + ns, sa, so.Err = syscall.Accept4(s, flags) + if err = af.apply(so); err != nil { + if so.Err == nil { + syscall.Close(ns) + } + return -1, nil, err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).AcceptFailed++ + return -1, nil, so.Err + } + nso := sw.addLocked(ns, so.Cookie.Family(), so.Cookie.Type(), so.Cookie.Protocol()) + sw.stats.getLocked(nso.Cookie).Accepted++ + return ns, sa, nil +} diff --git a/libgo/go/net/internal/socktest/sys_unix.go b/libgo/go/net/internal/socktest/sys_unix.go new file mode 100644 index 00000000000..f983e266f16 --- /dev/null +++ b/libgo/go/net/internal/socktest/sys_unix.go @@ -0,0 +1,193 @@ +// Copyright 2015 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 dragonfly freebsd linux nacl netbsd openbsd solaris + +package socktest + +import "syscall" + +// Socket wraps syscall.Socket. +func (sw *Switch) Socket(family, sotype, proto int) (s int, err error) { + sw.once.Do(sw.init) + + so := &Status{Cookie: cookie(family, sotype, proto)} + sw.fmu.RLock() + f, _ := sw.fltab[FilterSocket] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return -1, err + } + s, so.Err = syscall.Socket(family, sotype, proto) + if err = af.apply(so); err != nil { + if so.Err == nil { + syscall.Close(s) + } + return -1, err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).OpenFailed++ + return -1, so.Err + } + nso := sw.addLocked(s, family, sotype, proto) + sw.stats.getLocked(nso.Cookie).Opened++ + return s, nil +} + +// Close wraps syscall.Close. +func (sw *Switch) Close(s int) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Close(s) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterClose] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.Close(s) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).CloseFailed++ + return so.Err + } + delete(sw.sotab, s) + sw.stats.getLocked(so.Cookie).Closed++ + return nil +} + +// Connect wraps syscall.Connect. +func (sw *Switch) Connect(s int, sa syscall.Sockaddr) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Connect(s, sa) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterConnect] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.Connect(s, sa) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).ConnectFailed++ + return so.Err + } + sw.stats.getLocked(so.Cookie).Connected++ + return nil +} + +// Listen wraps syscall.Listen. +func (sw *Switch) Listen(s, backlog int) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Listen(s, backlog) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterListen] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.Listen(s, backlog) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).ListenFailed++ + return so.Err + } + sw.stats.getLocked(so.Cookie).Listened++ + return nil +} + +// Accept wraps syscall.Accept. +func (sw *Switch) Accept(s int) (ns int, sa syscall.Sockaddr, err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Accept(s) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterAccept] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return -1, nil, err + } + ns, sa, so.Err = syscall.Accept(s) + if err = af.apply(so); err != nil { + if so.Err == nil { + syscall.Close(ns) + } + return -1, nil, err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).AcceptFailed++ + return -1, nil, so.Err + } + nso := sw.addLocked(ns, so.Cookie.Family(), so.Cookie.Type(), so.Cookie.Protocol()) + sw.stats.getLocked(nso.Cookie).Accepted++ + return ns, sa, nil +} + +// GetsockoptInt wraps syscall.GetsockoptInt. +func (sw *Switch) GetsockoptInt(s, level, opt int) (soerr int, err error) { + so := sw.sockso(s) + if so == nil { + return syscall.GetsockoptInt(s, level, opt) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterGetsockoptInt] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return -1, err + } + soerr, so.Err = syscall.GetsockoptInt(s, level, opt) + so.SocketErr = syscall.Errno(soerr) + if err = af.apply(so); err != nil { + return -1, err + } + + if so.Err != nil { + return -1, so.Err + } + if opt == syscall.SO_ERROR && (so.SocketErr == syscall.Errno(0) || so.SocketErr == syscall.EISCONN) { + sw.smu.Lock() + sw.stats.getLocked(so.Cookie).Connected++ + sw.smu.Unlock() + } + return soerr, nil +} diff --git a/libgo/go/net/internal/socktest/sys_windows.go b/libgo/go/net/internal/socktest/sys_windows.go new file mode 100644 index 00000000000..e61bf2be605 --- /dev/null +++ b/libgo/go/net/internal/socktest/sys_windows.go @@ -0,0 +1,156 @@ +// Copyright 2015 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 socktest + +import "syscall" + +// Socket wraps syscall.Socket. +func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) { + sw.once.Do(sw.init) + + so := &Status{Cookie: cookie(family, sotype, proto)} + sw.fmu.RLock() + f, _ := sw.fltab[FilterSocket] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return syscall.InvalidHandle, err + } + s, so.Err = syscall.Socket(family, sotype, proto) + if err = af.apply(so); err != nil { + if so.Err == nil { + syscall.Closesocket(s) + } + return syscall.InvalidHandle, err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).OpenFailed++ + return syscall.InvalidHandle, so.Err + } + nso := sw.addLocked(s, family, sotype, proto) + sw.stats.getLocked(nso.Cookie).Opened++ + return s, nil +} + +// Closesocket wraps syscall.Closesocket. +func (sw *Switch) Closesocket(s syscall.Handle) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Closesocket(s) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterClose] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.Closesocket(s) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).CloseFailed++ + return so.Err + } + delete(sw.sotab, s) + sw.stats.getLocked(so.Cookie).Closed++ + return nil +} + +// Connect wraps syscall.Connect. +func (sw *Switch) Connect(s syscall.Handle, sa syscall.Sockaddr) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Connect(s, sa) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterConnect] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.Connect(s, sa) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).ConnectFailed++ + return so.Err + } + sw.stats.getLocked(so.Cookie).Connected++ + return nil +} + +// ConnectEx wraps syscall.ConnectEx. +func (sw *Switch) ConnectEx(s syscall.Handle, sa syscall.Sockaddr, b *byte, n uint32, nwr *uint32, o *syscall.Overlapped) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.ConnectEx(s, sa, b, n, nwr, o) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterConnect] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.ConnectEx(s, sa, b, n, nwr, o) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).ConnectFailed++ + return so.Err + } + sw.stats.getLocked(so.Cookie).Connected++ + return nil +} + +// Listen wraps syscall.Listen. +func (sw *Switch) Listen(s syscall.Handle, backlog int) (err error) { + so := sw.sockso(s) + if so == nil { + return syscall.Listen(s, backlog) + } + sw.fmu.RLock() + f, _ := sw.fltab[FilterListen] + sw.fmu.RUnlock() + + af, err := f.apply(so) + if err != nil { + return err + } + so.Err = syscall.Listen(s, backlog) + if err = af.apply(so); err != nil { + return err + } + + sw.smu.Lock() + defer sw.smu.Unlock() + if so.Err != nil { + sw.stats.getLocked(so.Cookie).ListenFailed++ + return so.Err + } + sw.stats.getLocked(so.Cookie).Listened++ + return nil +} diff --git a/libgo/go/net/ip.go b/libgo/go/net/ip.go index 4a93e97b39d..cc004d60729 100644 --- a/libgo/go/net/ip.go +++ b/libgo/go/net/ip.go @@ -12,8 +12,6 @@ package net -import "errors" - // IP address lengths (bytes). const ( IPv4len = 4 @@ -108,58 +106,57 @@ var ( IPv6linklocalallrouters = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02} ) -// IsUnspecified returns true if ip is an unspecified address. +// IsUnspecified reports whether ip is an unspecified address. func (ip IP) IsUnspecified() bool { - if ip.Equal(IPv4zero) || ip.Equal(IPv6unspecified) { - return true - } - return false + return ip.Equal(IPv4zero) || ip.Equal(IPv6unspecified) } -// IsLoopback returns true if ip is a loopback address. +// IsLoopback reports whether ip is a loopback address. func (ip IP) IsLoopback() bool { - if ip4 := ip.To4(); ip4 != nil && ip4[0] == 127 { - return true + if ip4 := ip.To4(); ip4 != nil { + return ip4[0] == 127 } return ip.Equal(IPv6loopback) } -// IsMulticast returns true if ip is a multicast address. +// IsMulticast reports whether ip is a multicast address. func (ip IP) IsMulticast() bool { - if ip4 := ip.To4(); ip4 != nil && ip4[0]&0xf0 == 0xe0 { - return true + if ip4 := ip.To4(); ip4 != nil { + return ip4[0]&0xf0 == 0xe0 } - return ip[0] == 0xff + return len(ip) == IPv6len && ip[0] == 0xff } -// IsInterfaceLinkLocalMulticast returns true if ip is +// IsInterfaceLocalMulticast reports whether ip is // an interface-local multicast address. func (ip IP) IsInterfaceLocalMulticast() bool { return len(ip) == IPv6len && ip[0] == 0xff && ip[1]&0x0f == 0x01 } -// IsLinkLocalMulticast returns true if ip is a link-local +// IsLinkLocalMulticast reports whether ip is a link-local // multicast address. func (ip IP) IsLinkLocalMulticast() bool { - if ip4 := ip.To4(); ip4 != nil && ip4[0] == 224 && ip4[1] == 0 && ip4[2] == 0 { - return true + if ip4 := ip.To4(); ip4 != nil { + return ip4[0] == 224 && ip4[1] == 0 && ip4[2] == 0 } - return ip[0] == 0xff && ip[1]&0x0f == 0x02 + return len(ip) == IPv6len && ip[0] == 0xff && ip[1]&0x0f == 0x02 } -// IsLinkLocalUnicast returns true if ip is a link-local +// IsLinkLocalUnicast reports whether ip is a link-local // unicast address. func (ip IP) IsLinkLocalUnicast() bool { - if ip4 := ip.To4(); ip4 != nil && ip4[0] == 169 && ip4[1] == 254 { - return true + if ip4 := ip.To4(); ip4 != nil { + return ip4[0] == 169 && ip4[1] == 254 } - return ip[0] == 0xfe && ip[1]&0xc0 == 0x80 + return len(ip) == IPv6len && ip[0] == 0xfe && ip[1]&0xc0 == 0x80 } -// IsGlobalUnicast returns true if ip is a global unicast +// IsGlobalUnicast reports whether ip is a global unicast // address. func (ip IP) IsGlobalUnicast() bool { - return !ip.IsUnspecified() && + return (len(ip) == IPv4len || len(ip) == IPv6len) && + !ip.Equal(IPv4bcast) && + !ip.IsUnspecified() && !ip.IsLoopback() && !ip.IsMulticast() && !ip.IsLinkLocalUnicast() @@ -267,10 +264,10 @@ func (ip IP) String() string { // If IPv4, use dotted notation. if p4 := p.To4(); len(p4) == IPv4len { - return itod(uint(p4[0])) + "." + - itod(uint(p4[1])) + "." + - itod(uint(p4[2])) + "." + - itod(uint(p4[3])) + return uitoa(uint(p4[0])) + "." + + uitoa(uint(p4[1])) + "." + + uitoa(uint(p4[2])) + "." + + uitoa(uint(p4[3])) } if len(p) != IPv6len { return "?" @@ -331,7 +328,7 @@ func (ip IP) MarshalText() ([]byte, error) { return []byte(""), nil } if len(ip) != IPv4len && len(ip) != IPv6len { - return nil, errors.New("invalid IP address") + return nil, &AddrError{Err: "invalid IP address", Addr: ip.String()} } return []byte(ip.String()), nil } @@ -346,13 +343,13 @@ func (ip *IP) UnmarshalText(text []byte) error { s := string(text) x := ParseIP(s) if x == nil { - return &ParseError{"IP address", s} + return &ParseError{Type: "IP address", Text: s} } *ip = x return nil } -// Equal returns true if ip and x are the same IP address. +// Equal reports whether ip and x are the same IP address. // An IPv4 address and that same address in IPv6 form are // considered to be equal. func (ip IP) Equal(x IP) bool { @@ -491,7 +488,7 @@ func (n *IPNet) String() string { if l == -1 { return nn.String() + "/" + m.String() } - return nn.String() + "/" + itod(uint(l)) + return nn.String() + "/" + uitoa(uint(l)) } // Parse IPv4 address (d.d.d.d). @@ -633,16 +630,6 @@ func parseIPv6(s string, zoneAllowed bool) (ip IP, zone string) { return ip, zone } -// A ParseError represents a malformed text string and the type of string that was expected. -type ParseError struct { - Type string - Text string -} - -func (e *ParseError) Error() string { - return "invalid " + e.Type + ": " + e.Text -} - // ParseIP parses s as an IP address, returning the result. // The string s can be in dotted decimal ("74.125.19.99") // or IPv6 ("2001:4860:0:2001::68") form. @@ -671,7 +658,7 @@ func ParseIP(s string) IP { func ParseCIDR(s string) (IP, *IPNet, error) { i := byteIndex(s, '/') if i < 0 { - return nil, nil, &ParseError{"CIDR address", s} + return nil, nil, &ParseError{Type: "CIDR address", Text: s} } addr, mask := s[:i], s[i+1:] iplen := IPv4len @@ -682,7 +669,7 @@ func ParseCIDR(s string) (IP, *IPNet, error) { } n, i, ok := dtoi(mask, 0) if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen { - return nil, nil, &ParseError{"CIDR address", s} + return nil, nil, &ParseError{Type: "CIDR address", Text: s} } m := CIDRMask(n, 8*iplen) return ip, &IPNet{IP: ip.Mask(m), Mask: m}, nil diff --git a/libgo/go/net/ip_test.go b/libgo/go/net/ip_test.go index 485ff51153b..3d95a73c097 100644 --- a/libgo/go/net/ip_test.go +++ b/libgo/go/net/ip_test.go @@ -16,12 +16,20 @@ var parseIPTests = []struct { }{ {"127.0.1.2", IPv4(127, 0, 1, 2)}, {"127.0.0.1", IPv4(127, 0, 0, 1)}, + {"127.001.002.003", IPv4(127, 1, 2, 3)}, + {"::ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, + {"::ffff:127.001.002.003", IPv4(127, 1, 2, 3)}, + {"::ffff:7f01:0203", IPv4(127, 1, 2, 3)}, + {"0:0:0:0:0000:ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, + {"0:0:0:0:000000:ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, + {"0:0:0:0::ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, + + {"2001:4860:0:2001::68", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}}, + {"2001:4860:0000:2001:0000:0000:0000:0068", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}}, + {"127.0.0.256", nil}, {"abc", nil}, {"123:", nil}, - {"::ffff:127.0.0.1", IPv4(127, 0, 0, 1)}, - {"2001:4860:0:2001::68", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}}, - {"::ffff:4a7d:1363", IPv4(74, 125, 19, 99)}, {"fe80::1%lo0", nil}, {"fe80::1%911", nil}, {"", nil}, @@ -44,7 +52,52 @@ func TestParseIP(t *testing.T) { } } +func TestLookupWithIP(t *testing.T) { + _, err := LookupIP("") + if err == nil { + t.Errorf(`LookupIP("") succeeded, should fail`) + } + _, err = LookupHost("") + if err == nil { + t.Errorf(`LookupIP("") succeeded, should fail`) + } + + // Test that LookupHost and LookupIP, which normally + // expect host names, work with IP addresses. + for _, tt := range parseIPTests { + if tt.out != nil { + addrs, err := LookupHost(tt.in) + if len(addrs) != 1 || addrs[0] != tt.in || err != nil { + t.Errorf("LookupHost(%q) = %v, %v, want %v, nil", tt.in, addrs, err, []string{tt.in}) + } + } else if !testing.Short() { + // We can't control what the host resolver does; if it can resolve, say, + // 127.0.0.256 or fe80::1%911 or a host named 'abc', who are we to judge? + // Warn about these discrepancies but don't fail the test. + addrs, err := LookupHost(tt.in) + if err == nil { + t.Logf("warning: LookupHost(%q) = %v, want error", tt.in, addrs) + } + } + + if tt.out != nil { + ips, err := LookupIP(tt.in) + if len(ips) != 1 || !reflect.DeepEqual(ips[0], tt.out) || err != nil { + t.Errorf("LookupIP(%q) = %v, %v, want %v, nil", tt.in, ips, err, []IP{tt.out}) + } + } else if !testing.Short() { + ips, err := LookupIP(tt.in) + // We can't control what the host resolver does. See above. + if err == nil { + t.Logf("warning: LookupIP(%q) = %v, want error", tt.in, ips) + } + } + } +} + func BenchmarkParseIP(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { for _, tt := range parseIPTests { ParseIP(tt.in) @@ -100,6 +153,8 @@ func TestIPString(t *testing.T) { } func BenchmarkIPString(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { for _, tt := range ipStringTests { if tt.in != nil { @@ -150,6 +205,8 @@ func TestIPMaskString(t *testing.T) { } func BenchmarkIPMaskString(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + for i := 0; i < b.N; i++ { for _, tt := range ipMaskStringTests { tt.in.String() @@ -180,10 +237,10 @@ var parseCIDRTests = []struct { {"abcd:2345::/24", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2300::"), Mask: IPMask(ParseIP("ffff:ff00::"))}, nil}, {"2001:DB8::/48", ParseIP("2001:DB8::"), &IPNet{IP: ParseIP("2001:DB8::"), Mask: IPMask(ParseIP("ffff:ffff:ffff::"))}, nil}, {"2001:DB8::1/48", ParseIP("2001:DB8::1"), &IPNet{IP: ParseIP("2001:DB8::"), Mask: IPMask(ParseIP("ffff:ffff:ffff::"))}, nil}, - {"192.168.1.1/255.255.255.0", nil, nil, &ParseError{"CIDR address", "192.168.1.1/255.255.255.0"}}, - {"192.168.1.1/35", nil, nil, &ParseError{"CIDR address", "192.168.1.1/35"}}, - {"2001:db8::1/-1", nil, nil, &ParseError{"CIDR address", "2001:db8::1/-1"}}, - {"", nil, nil, &ParseError{"CIDR address", ""}}, + {"192.168.1.1/255.255.255.0", nil, nil, &ParseError{Type: "CIDR address", Text: "192.168.1.1/255.255.255.0"}}, + {"192.168.1.1/35", nil, nil, &ParseError{Type: "CIDR address", Text: "192.168.1.1/35"}}, + {"2001:db8::1/-1", nil, nil, &ParseError{Type: "CIDR address", Text: "2001:db8::1/-1"}}, + {"", nil, nil, &ParseError{Type: "CIDR address", Text: ""}}, } func TestParseCIDR(t *testing.T) { @@ -425,31 +482,44 @@ var ipAddrScopeTests = []struct { {IP.IsUnspecified, IPv4(127, 0, 0, 1), false}, {IP.IsUnspecified, IPv6unspecified, true}, {IP.IsUnspecified, IPv6interfacelocalallnodes, false}, + {IP.IsUnspecified, nil, false}, {IP.IsLoopback, IPv4(127, 0, 0, 1), true}, {IP.IsLoopback, IPv4(127, 255, 255, 254), true}, {IP.IsLoopback, IPv4(128, 1, 2, 3), false}, {IP.IsLoopback, IPv6loopback, true}, {IP.IsLoopback, IPv6linklocalallrouters, false}, + {IP.IsLoopback, nil, false}, {IP.IsMulticast, IPv4(224, 0, 0, 0), true}, {IP.IsMulticast, IPv4(239, 0, 0, 0), true}, {IP.IsMulticast, IPv4(240, 0, 0, 0), false}, {IP.IsMulticast, IPv6linklocalallnodes, true}, {IP.IsMulticast, IP{0xff, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, {IP.IsMulticast, IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {IP.IsMulticast, nil, false}, + {IP.IsInterfaceLocalMulticast, IPv4(224, 0, 0, 0), false}, + {IP.IsInterfaceLocalMulticast, IPv4(0xff, 0x01, 0, 0), false}, + {IP.IsInterfaceLocalMulticast, IPv6interfacelocalallnodes, true}, + {IP.IsInterfaceLocalMulticast, nil, false}, {IP.IsLinkLocalMulticast, IPv4(224, 0, 0, 0), true}, {IP.IsLinkLocalMulticast, IPv4(239, 0, 0, 0), false}, + {IP.IsLinkLocalMulticast, IPv4(0xff, 0x02, 0, 0), false}, {IP.IsLinkLocalMulticast, IPv6linklocalallrouters, true}, {IP.IsLinkLocalMulticast, IP{0xff, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {IP.IsLinkLocalMulticast, nil, false}, {IP.IsLinkLocalUnicast, IPv4(169, 254, 0, 0), true}, {IP.IsLinkLocalUnicast, IPv4(169, 255, 0, 0), false}, + {IP.IsLinkLocalUnicast, IPv4(0xfe, 0x80, 0, 0), false}, {IP.IsLinkLocalUnicast, IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, {IP.IsLinkLocalUnicast, IP{0xfe, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {IP.IsLinkLocalUnicast, nil, false}, {IP.IsGlobalUnicast, IPv4(240, 0, 0, 0), true}, {IP.IsGlobalUnicast, IPv4(232, 0, 0, 0), false}, {IP.IsGlobalUnicast, IPv4(169, 254, 0, 0), false}, + {IP.IsGlobalUnicast, IPv4bcast, false}, {IP.IsGlobalUnicast, IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0x1, 0x23, 0, 0x12, 0, 0x1}, true}, {IP.IsGlobalUnicast, IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {IP.IsGlobalUnicast, IP{0xff, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {IP.IsGlobalUnicast, nil, false}, } func name(f interface{}) string { @@ -461,5 +531,12 @@ func TestIPAddrScope(t *testing.T) { if ok := tt.scope(tt.in); ok != tt.ok { t.Errorf("%s(%q) = %v, want %v", name(tt.scope), tt.in, ok, tt.ok) } + ip := tt.in.To4() + if ip == nil { + continue + } + if ok := tt.scope(ip); ok != tt.ok { + t.Errorf("%s(%q) = %v, want %v", name(tt.scope), ip, ok, tt.ok) + } } } diff --git a/libgo/go/net/ipraw_test.go b/libgo/go/net/ipraw_test.go index 92dc8dc5694..5d86a9d0316 100644 --- a/libgo/go/net/ipraw_test.go +++ b/libgo/go/net/ipraw_test.go @@ -5,17 +5,18 @@ package net import ( - "bytes" - "fmt" - "os" "reflect" - "runtime" "testing" - "time" ) +// The full stack test cases for IPConn have been moved to the +// following: +// golang.org/x/net/ipv4 +// golang.org/x/net/ipv6 +// golang.org/x/net/icmp + type resolveIPAddrTest struct { - net string + network string litAddrOrName string addr *IPAddr err error @@ -37,210 +38,41 @@ var resolveIPAddrTests = []resolveIPAddrTest{ {"", "127.0.0.1", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, // Go 1.0 behavior {"", "::1", &IPAddr{IP: ParseIP("::1")}, nil}, // Go 1.0 behavior + {"ip4:icmp", "", &IPAddr{}, nil}, + {"l2tp", "127.0.0.1", nil, UnknownNetworkError("l2tp")}, {"l2tp:gre", "127.0.0.1", nil, UnknownNetworkError("l2tp:gre")}, {"tcp", "1.2.3.4:123", nil, UnknownNetworkError("tcp")}, } -func init() { - if ifi := loopbackInterface(); ifi != nil { - index := fmt.Sprintf("%v", ifi.Index) - resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{ - {"ip6", "fe80::1%" + ifi.Name, &IPAddr{IP: ParseIP("fe80::1"), Zone: zoneToString(ifi.Index)}, nil}, - {"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil}, - }...) - } - if ips, err := LookupIP("localhost"); err == nil && len(ips) > 1 && supportsIPv4 && supportsIPv6 { - resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{ - {"ip", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, - {"ip4", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, - {"ip6", "localhost", &IPAddr{IP: IPv6loopback}, nil}, - }...) - } -} - -func skipRawSocketTest(t *testing.T) (skip bool, skipmsg string) { - skip, skipmsg, err := skipRawSocketTests() - if err != nil { - t.Fatal(err) - } - return skip, skipmsg -} - func TestResolveIPAddr(t *testing.T) { - switch runtime.GOOS { - case "nacl": - t.Skipf("skipping test on %q", runtime.GOOS) + if !testableNetwork("ip+nopriv") { + t.Skip("ip+nopriv test") } - for _, tt := range resolveIPAddrTests { - addr, err := ResolveIPAddr(tt.net, tt.litAddrOrName) + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = lookupLocalhost + + for i, tt := range resolveIPAddrTests { + addr, err := ResolveIPAddr(tt.network, tt.litAddrOrName) if err != tt.err { - t.Fatalf("ResolveIPAddr(%v, %v) failed: %v", tt.net, tt.litAddrOrName, err) + t.Errorf("#%d: %v", i, err) } else if !reflect.DeepEqual(addr, tt.addr) { - t.Fatalf("got %#v; expected %#v", addr, tt.addr) + t.Errorf("#%d: got %#v; want %#v", i, addr, tt.addr) } - } -} - -var icmpEchoTests = []struct { - net string - laddr string - raddr string -}{ - {"ip4:icmp", "0.0.0.0", "127.0.0.1"}, - {"ip6:ipv6-icmp", "::", "::1"}, -} - -func TestConnICMPEcho(t *testing.T) { - if skip, skipmsg := skipRawSocketTest(t); skip { - t.Skip(skipmsg) - } - - for i, tt := range icmpEchoTests { - net, _, err := parseNetwork(tt.net) if err != nil { - t.Fatalf("parseNetwork failed: %v", err) - } - if net == "ip6" && !supportsIPv6 { continue } - - c, err := Dial(tt.net, tt.raddr) - if err != nil { - t.Fatalf("Dial failed: %v", err) - } - c.SetDeadline(time.Now().Add(100 * time.Millisecond)) - defer c.Close() - - typ := icmpv4EchoRequest - if net == "ip6" { - typ = icmpv6EchoRequest - } - xid, xseq := os.Getpid()&0xffff, i+1 - wb, err := (&icmpMessage{ - Type: typ, Code: 0, - Body: &icmpEcho{ - ID: xid, Seq: xseq, - Data: bytes.Repeat([]byte("Go Go Gadget Ping!!!"), 3), - }, - }).Marshal() + rtaddr, err := ResolveIPAddr(addr.Network(), addr.String()) if err != nil { - t.Fatalf("icmpMessage.Marshal failed: %v", err) - } - if _, err := c.Write(wb); err != nil { - t.Fatalf("Conn.Write failed: %v", err) - } - var m *icmpMessage - rb := make([]byte, 20+len(wb)) - for { - if _, err := c.Read(rb); err != nil { - t.Fatalf("Conn.Read failed: %v", err) - } - if net == "ip4" { - rb = ipv4Payload(rb) - } - if m, err = parseICMPMessage(rb); err != nil { - t.Fatalf("parseICMPMessage failed: %v", err) - } - switch m.Type { - case icmpv4EchoRequest, icmpv6EchoRequest: - continue - } - break - } - switch p := m.Body.(type) { - case *icmpEcho: - if p.ID != xid || p.Seq != xseq { - t.Fatalf("got id=%v, seqnum=%v; expected id=%v, seqnum=%v", p.ID, p.Seq, xid, xseq) - } - default: - t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, typ, 0) + t.Errorf("#%d: %v", i, err) + } else if !reflect.DeepEqual(rtaddr, addr) { + t.Errorf("#%d: got %#v; want %#v", i, rtaddr, addr) } } } -func TestPacketConnICMPEcho(t *testing.T) { - if skip, skipmsg := skipRawSocketTest(t); skip { - t.Skip(skipmsg) - } - - for i, tt := range icmpEchoTests { - net, _, err := parseNetwork(tt.net) - if err != nil { - t.Fatalf("parseNetwork failed: %v", err) - } - if net == "ip6" && !supportsIPv6 { - continue - } - - c, err := ListenPacket(tt.net, tt.laddr) - if err != nil { - t.Fatalf("ListenPacket failed: %v", err) - } - c.SetDeadline(time.Now().Add(100 * time.Millisecond)) - defer c.Close() - - ra, err := ResolveIPAddr(tt.net, tt.raddr) - if err != nil { - t.Fatalf("ResolveIPAddr failed: %v", err) - } - typ := icmpv4EchoRequest - if net == "ip6" { - typ = icmpv6EchoRequest - } - xid, xseq := os.Getpid()&0xffff, i+1 - wb, err := (&icmpMessage{ - Type: typ, Code: 0, - Body: &icmpEcho{ - ID: xid, Seq: xseq, - Data: bytes.Repeat([]byte("Go Go Gadget Ping!!!"), 3), - }, - }).Marshal() - if err != nil { - t.Fatalf("icmpMessage.Marshal failed: %v", err) - } - if _, err := c.WriteTo(wb, ra); err != nil { - t.Fatalf("PacketConn.WriteTo failed: %v", err) - } - var m *icmpMessage - rb := make([]byte, 20+len(wb)) - for { - if _, _, err := c.ReadFrom(rb); err != nil { - t.Fatalf("PacketConn.ReadFrom failed: %v", err) - } - // See BUG section. - //if net == "ip4" { - // rb = ipv4Payload(rb) - //} - if m, err = parseICMPMessage(rb); err != nil { - t.Fatalf("parseICMPMessage failed: %v", err) - } - switch m.Type { - case icmpv4EchoRequest, icmpv6EchoRequest: - continue - } - break - } - switch p := m.Body.(type) { - case *icmpEcho: - if p.ID != xid || p.Seq != xseq { - t.Fatalf("got id=%v, seqnum=%v; expected id=%v, seqnum=%v", p.ID, p.Seq, xid, xseq) - } - default: - t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, typ, 0) - } - } -} - -func ipv4Payload(b []byte) []byte { - if len(b) < 20 { - return b - } - hdrlen := int(b[0]&0x0f) << 2 - return b[hdrlen:] -} - var ipConnLocalNameTests = []struct { net string laddr *IPAddr @@ -251,44 +83,34 @@ var ipConnLocalNameTests = []struct { } func TestIPConnLocalName(t *testing.T) { - switch runtime.GOOS { - case "nacl", "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) - default: - if os.Getuid() != 0 { - t.Skip("skipping test; must be root") - } - } - for _, tt := range ipConnLocalNameTests { + if !testableNetwork(tt.net) { + t.Logf("skipping %s test", tt.net) + continue + } c, err := ListenIP(tt.net, tt.laddr) if err != nil { - t.Fatalf("ListenIP failed: %v", err) + t.Fatal(err) } defer c.Close() if la := c.LocalAddr(); la == nil { - t.Fatal("IPConn.LocalAddr failed") + t.Fatal("should not fail") } } } func TestIPConnRemoteName(t *testing.T) { - switch runtime.GOOS { - case "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) - default: - if os.Getuid() != 0 { - t.Skip("skipping test; must be root") - } + if !testableNetwork("ip:tcp") { + t.Skip("ip:tcp test") } raddr := &IPAddr{IP: IPv4(127, 0, 0, 1).To4()} c, err := DialIP("ip:tcp", &IPAddr{IP: IPv4(127, 0, 0, 1)}, raddr) if err != nil { - t.Fatalf("DialIP failed: %v", err) + t.Fatal(err) } defer c.Close() if !reflect.DeepEqual(raddr, c.RemoteAddr()) { - t.Fatalf("got %#v, expected %#v", c.RemoteAddr(), raddr) + t.Fatalf("got %#v; want %#v", c.RemoteAddr(), raddr) } } diff --git a/libgo/go/net/iprawsock.go b/libgo/go/net/iprawsock.go index 5cc361390ff..f02df7fa8d1 100644 --- a/libgo/go/net/iprawsock.go +++ b/libgo/go/net/iprawsock.go @@ -17,13 +17,21 @@ func (a *IPAddr) String() string { if a == nil { return "<nil>" } + ip := ipEmptyString(a.IP) if a.Zone != "" { - return a.IP.String() + "%" + a.Zone + return ip + "%" + a.Zone } - return a.IP.String() + return ip } -func (a *IPAddr) toAddr() Addr { +func (a *IPAddr) isWildcard() bool { + if a == nil || a.IP == nil { + return true + } + return a.IP.IsUnspecified() +} + +func (a *IPAddr) opAddr() Addr { if a == nil { return nil } @@ -46,9 +54,9 @@ func ResolveIPAddr(net, addr string) (*IPAddr, error) { default: return nil, UnknownNetworkError(net) } - a, err := resolveInternetAddr(afnet, addr, noDeadline) + addrs, err := internetAddrList(afnet, addr, noDeadline) if err != nil { return nil, err } - return a.toAddr().(*IPAddr), nil + return addrs.first(isIPv4).(*IPAddr), nil } diff --git a/libgo/go/net/iprawsock_plan9.go b/libgo/go/net/iprawsock_plan9.go index e62d116b817..b027adc53a7 100644 --- a/libgo/go/net/iprawsock_plan9.go +++ b/libgo/go/net/iprawsock_plan9.go @@ -23,12 +23,12 @@ type IPConn struct { // Timeout() == true after a fixed time limit; see SetDeadline and // SetReadDeadline. func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error) { - return 0, nil, syscall.EPLAN9 + return 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // ReadFrom implements the PacketConn ReadFrom method. func (c *IPConn) ReadFrom(b []byte) (int, Addr, error) { - return 0, nil, syscall.EPLAN9 + return 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // ReadMsgIP reads a packet from c, copying the payload into b and the @@ -36,7 +36,7 @@ func (c *IPConn) ReadFrom(b []byte) (int, Addr, error) { // bytes copied into b, the number of bytes copied into oob, the flags // that were set on the packet and the source address of the packet. func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error) { - return 0, 0, 0, nil, syscall.EPLAN9 + return 0, 0, 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // WriteToIP writes an IP packet to addr via c, copying the payload @@ -47,19 +47,19 @@ func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err // SetWriteDeadline. On packet-oriented connections, write timeouts // are rare. func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) { - return 0, syscall.EPLAN9 + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EPLAN9} } // WriteTo implements the PacketConn WriteTo method. func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) { - return 0, syscall.EPLAN9 + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr, Err: syscall.EPLAN9} } // WriteMsgIP writes a packet to addr via c, copying the payload from // b and the associated out-of-band data from oob. It returns the // number of payload and out-of-band bytes written. func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error) { - return 0, 0, syscall.EPLAN9 + return 0, 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EPLAN9} } // DialIP connects to the remote address raddr on the network protocol @@ -70,7 +70,7 @@ func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) { } func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: syscall.EPLAN9} } // ListenIP listens for incoming IP packets addressed to the local @@ -78,5 +78,5 @@ func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, // methods can be used to receive and send IP packets with per-packet // addressing. func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "listen", Net: netProto, Source: nil, Addr: laddr.opAddr(), Err: syscall.EPLAN9} } diff --git a/libgo/go/net/iprawsock_posix.go b/libgo/go/net/iprawsock_posix.go index 99b081ba8c8..9417606ce94 100644 --- a/libgo/go/net/iprawsock_posix.go +++ b/libgo/go/net/iprawsock_posix.go @@ -43,13 +43,6 @@ func (a *IPAddr) family() int { return syscall.AF_INET6 } -func (a *IPAddr) isWildcard() bool { - if a == nil || a.IP == nil { - return true - } - return a.IP.IsUnspecified() -} - func (a *IPAddr) sockaddr(family int) (syscall.Sockaddr, error) { if a == nil { return nil, nil @@ -83,24 +76,41 @@ func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error) { switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &IPAddr{IP: sa.Addr[0:]} - if len(b) >= IPv4len { // discard ipv4 header - hsize := (int(b[0]) & 0xf) * 4 - copy(b, b[hsize:]) - n -= hsize - } + n = stripIPv4Header(n, b) case *syscall.SockaddrInet6: addr = &IPAddr{IP: sa.Addr[0:], Zone: zoneToString(int(sa.ZoneId))} } + if err != nil { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } return n, addr, err } +func stripIPv4Header(n int, b []byte) int { + if len(b) < 20 { + return n + } + l := int(b[0]&0x0f) << 2 + if 20 > l || l > len(b) { + return n + } + if b[0]>>4 != 4 { + return n + } + copy(b, b[l:]) + return n - l +} + // ReadFrom implements the PacketConn ReadFrom method. func (c *IPConn) ReadFrom(b []byte) (int, Addr, error) { if !c.ok() { return 0, nil, syscall.EINVAL } n, addr, err := c.ReadFromIP(b) - return n, addr.toAddr(), err + if addr == nil { + return n, nil, err + } + return n, addr, err } // ReadMsgIP reads a packet from c, copying the payload into b and the @@ -119,6 +129,9 @@ func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err case *syscall.SockaddrInet6: addr = &IPAddr{IP: sa.Addr[0:], Zone: zoneToString(int(sa.ZoneId))} } + if err != nil { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } return } @@ -134,16 +147,20 @@ func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) { return 0, syscall.EINVAL } if c.fd.isConnected { - return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: ErrWriteToConnected} } if addr == nil { - return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: nil, Err: errMissingAddress} } sa, err := addr.sockaddr(c.fd.family) if err != nil { - return 0, &OpError{"write", c.fd.net, addr, err} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + n, err := c.fd.writeTo(b, sa) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} } - return c.fd.writeTo(b, sa) + return n, err } // WriteTo implements the PacketConn WriteTo method. @@ -153,7 +170,7 @@ func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) { } a, ok := addr.(*IPAddr) if !ok { - return 0, &OpError{"write", c.fd.net, addr, syscall.EINVAL} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL} } return c.WriteToIP(b, a) } @@ -166,16 +183,21 @@ func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error return 0, 0, syscall.EINVAL } if c.fd.isConnected { - return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: ErrWriteToConnected} } if addr == nil { - return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: nil, Err: errMissingAddress} } - sa, err := addr.sockaddr(c.fd.family) + var sa syscall.Sockaddr + sa, err = addr.sockaddr(c.fd.family) if err != nil { - return 0, 0, &OpError{"write", c.fd.net, addr, err} + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} } - return c.fd.writeMsg(b, oob, sa) + n, oobn, err = c.fd.writeMsg(b, oob, sa) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + return } // DialIP connects to the remote address raddr on the network protocol @@ -188,19 +210,19 @@ func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) { func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, error) { net, proto, err := parseNetwork(netProto) if err != nil { - return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err} + return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err} } switch net { case "ip", "ip4", "ip6": default: - return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: UnknownNetworkError(netProto)} + return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(netProto)} } if raddr == nil { - return nil, &OpError{Op: "dial", Net: netProto, Addr: nil, Err: errMissingAddress} + return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress} } fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_RAW, proto, "dial") if err != nil { - return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err} + return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err} } return newIPConn(fd), nil } @@ -212,16 +234,16 @@ func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error) { net, proto, err := parseNetwork(netProto) if err != nil { - return nil, &OpError{Op: "dial", Net: netProto, Addr: laddr, Err: err} + return nil, &OpError{Op: "listen", Net: netProto, Source: nil, Addr: laddr.opAddr(), Err: err} } switch net { case "ip", "ip4", "ip6": default: - return nil, &OpError{Op: "listen", Net: netProto, Addr: laddr, Err: UnknownNetworkError(netProto)} + return nil, &OpError{Op: "listen", Net: netProto, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(netProto)} } fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_RAW, proto, "listen") if err != nil { - return nil, &OpError{Op: "listen", Net: netProto, Addr: laddr, Err: err} + return nil, &OpError{Op: "listen", Net: netProto, Source: nil, Addr: laddr.opAddr(), Err: err} } return newIPConn(fd), nil } diff --git a/libgo/go/net/ipsock.go b/libgo/go/net/ipsock.go index dda85780308..6e75c33d534 100644 --- a/libgo/go/net/ipsock.go +++ b/libgo/go/net/ipsock.go @@ -26,113 +26,82 @@ var ( supportsIPv4map bool ) -func init() { - sysInit() - supportsIPv4 = probeIPv4Stack() - supportsIPv6, supportsIPv4map = probeIPv6Stack() -} +// An addrList represents a list of network endpoint addresses. +type addrList []Addr -// A netaddr represents a network endpoint address or a list of -// network endpoint addresses. -type netaddr interface { - // toAddr returns the address represented in Addr interface. - // It returns a nil interface when the address is nil. - toAddr() Addr +// isIPv4 returns true if the Addr contains an IPv4 address. +func isIPv4(addr Addr) bool { + switch addr := addr.(type) { + case *TCPAddr: + return addr.IP.To4() != nil + case *UDPAddr: + return addr.IP.To4() != nil + case *IPAddr: + return addr.IP.To4() != nil + } + return false } -// An addrList represents a list of network endpoint addresses. -type addrList []netaddr +// first returns the first address which satisfies strategy, or if +// none do, then the first address of any kind. +func (addrs addrList) first(strategy func(Addr) bool) Addr { + for _, addr := range addrs { + if strategy(addr) { + return addr + } + } + return addrs[0] +} -func (al addrList) toAddr() Addr { - switch len(al) { - case 0: - return nil - case 1: - return al[0].toAddr() - default: - // For now, we'll roughly pick first one without - // considering dealing with any preferences such as - // DNS TTL, transport path quality, network routing - // information. - return al[0].toAddr() +// partition divides an address list into two categories, using a +// strategy function to assign a boolean label to each address. +// The first address, and any with a matching label, are returned as +// primaries, while addresses with the opposite label are returned +// as fallbacks. For non-empty inputs, primaries is guaranteed to be +// non-empty. +func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks addrList) { + var primaryLabel bool + for i, addr := range addrs { + label := strategy(addr) + if i == 0 || label == primaryLabel { + primaryLabel = label + primaries = append(primaries, addr) + } else { + fallbacks = append(fallbacks, addr) + } } + return } var errNoSuitableAddress = errors.New("no suitable address found") -// firstFavoriteAddr returns an address or a list of addresses that -// implement the netaddr interface. Known filters are nil, ipv4only -// and ipv6only. It returns any address when filter is nil. The result -// contains at least one address when error is nil. -func firstFavoriteAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) { - if filter != nil { - return firstSupportedAddr(filter, ips, inetaddr) - } - var ( - ipv4, ipv6, swap bool - list addrList - ) +// filterAddrList applies a filter to a list of IP addresses, +// yielding a list of Addr objects. Known filters are nil, ipv4only, +// and ipv6only. It returns every address when the filter is nil. +// The result contains at least one address when error is nil. +func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr) (addrList, error) { + var addrs addrList for _, ip := range ips { - // We'll take any IP address, but since the dialing - // code does not yet try multiple addresses - // effectively, prefer to use an IPv4 address if - // possible. This is especially relevant if localhost - // resolves to [ipv6-localhost, ipv4-localhost]. Too - // much code assumes localhost == ipv4-localhost. - if ip4 := ipv4only(ip); ip4 != nil && !ipv4 { - list = append(list, inetaddr(ip4)) - ipv4 = true - if ipv6 { - swap = true - } - } else if ip6 := ipv6only(ip); ip6 != nil && !ipv6 { - list = append(list, inetaddr(ip6)) - ipv6 = true - } - if ipv4 && ipv6 { - if swap { - list[0], list[1] = list[1], list[0] - } - break + if filter == nil || filter(ip) { + addrs = append(addrs, inetaddr(ip)) } } - switch len(list) { - case 0: + if len(addrs) == 0 { return nil, errNoSuitableAddress - case 1: - return list[0], nil - default: - return list, nil - } -} - -func firstSupportedAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) { - for _, ip := range ips { - if ip := filter(ip); ip != nil { - return inetaddr(ip), nil - } } - return nil, errNoSuitableAddress + return addrs, nil } -// ipv4only returns IPv4 addresses that we can use with the kernel's -// IPv4 addressing modes. If ip is an IPv4 address, ipv4only returns ip. -// Otherwise it returns nil. -func ipv4only(ip IP) IP { - if supportsIPv4 && ip.To4() != nil { - return ip - } - return nil +// ipv4only reports whether the kernel supports IPv4 addressing mode +// and addr is an IPv4 address. +func ipv4only(addr IPAddr) bool { + return supportsIPv4 && addr.IP.To4() != nil } -// ipv6only returns IPv6 addresses that we can use with the kernel's -// IPv6 addressing modes. It returns IPv4-mapped IPv6 addresses as -// nils and returns other IPv6 address types as IPv6 addresses. -func ipv6only(ip IP) IP { - if supportsIPv6 && len(ip) == IPv6len && ip.To4() == nil { - return ip - } - return nil +// ipv6only reports whether the kernel supports IPv6 addressing mode +// and addr is an IPv6 address except IPv4-mapped IPv6 address. +func ipv6only(addr IPAddr) bool { + return supportsIPv6 && len(addr.IP) == IPv6len && addr.IP.To4() == nil } // SplitHostPort splits a network address of the form "host:port", @@ -153,7 +122,7 @@ func SplitHostPort(hostport string) (host, port string, err error) { // Expect the first ']' just before the last ':'. end := byteIndex(hostport, ']') if end < 0 { - err = &AddrError{"missing ']' in address", hostport} + err = &AddrError{Err: "missing ']' in address", Addr: hostport} return } switch end + 1 { @@ -182,11 +151,11 @@ func SplitHostPort(hostport string) (host, port string, err error) { } } if byteIndex(hostport[j:], '[') >= 0 { - err = &AddrError{"unexpected '[' in address", hostport} + err = &AddrError{Err: "unexpected '[' in address", Addr: hostport} return } if byteIndex(hostport[k:], ']') >= 0 { - err = &AddrError{"unexpected ']' in address", hostport} + err = &AddrError{Err: "unexpected ']' in address", Addr: hostport} return } @@ -194,15 +163,15 @@ func SplitHostPort(hostport string) (host, port string, err error) { return missingPort: - err = &AddrError{"missing port in address", hostport} + err = &AddrError{Err: "missing port in address", Addr: hostport} return tooManyColons: - err = &AddrError{"too many colons in address", hostport} + err = &AddrError{Err: "too many colons in address", Addr: hostport} return missingBrackets: - err = &AddrError{"missing brackets in address", hostport} + err = &AddrError{Err: "missing brackets in address", Addr: hostport} return } @@ -228,17 +197,15 @@ func JoinHostPort(host, port string) string { return host + ":" + port } -// resolveInternetAddr resolves addr that is either a literal IP -// address or a DNS name and returns an internet protocol family -// address. It returns a list that contains a pair of different -// address family addresses when addr is a DNS name and the name has -// multiple address family records. The result contains at least one -// address when error is nil. -func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) { +// internetAddrList resolves addr, which may be a literal IP +// address or a DNS name, and returns a list of internet protocol +// family addresses. The result contains at least one address when +// error is nil. +func internetAddrList(net, addr string, deadline time.Time) (addrList, error) { var ( - err error - host, port, zone string - portnum int + err error + host, port string + portnum int ) switch net { case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": @@ -257,43 +224,43 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) default: return nil, UnknownNetworkError(net) } - inetaddr := func(ip IP) netaddr { + inetaddr := func(ip IPAddr) Addr { switch net { case "tcp", "tcp4", "tcp6": - return &TCPAddr{IP: ip, Port: portnum, Zone: zone} + return &TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} case "udp", "udp4", "udp6": - return &UDPAddr{IP: ip, Port: portnum, Zone: zone} + return &UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} case "ip", "ip4", "ip6": - return &IPAddr{IP: ip, Zone: zone} + return &IPAddr{IP: ip.IP, Zone: ip.Zone} default: panic("unexpected network: " + net) } } if host == "" { - return inetaddr(nil), nil + return addrList{inetaddr(IPAddr{})}, nil } // Try as a literal IP address. var ip IP if ip = parseIPv4(host); ip != nil { - return inetaddr(ip), nil + return addrList{inetaddr(IPAddr{IP: ip})}, nil } + var zone string if ip, zone = parseIPv6(host, true); ip != nil { - return inetaddr(ip), nil + return addrList{inetaddr(IPAddr{IP: ip, Zone: zone})}, nil } // Try as a DNS name. - host, zone = splitHostZone(host) ips, err := lookupIPDeadline(host, deadline) if err != nil { return nil, err } - var filter func(IP) IP + var filter func(IPAddr) bool if net != "" && net[len(net)-1] == '4' { filter = ipv4only } - if net != "" && net[len(net)-1] == '6' || zone != "" { + if net != "" && net[len(net)-1] == '6' { filter = ipv6only } - return firstFavoriteAddr(filter, ips, inetaddr) + return filterAddrList(filter, ips, inetaddr) } func zoneToString(zone int) string { @@ -303,7 +270,7 @@ func zoneToString(zone int) string { if ifi, err := InterfaceByIndex(zone); err == nil { return ifi.Name } - return itod(uint(zone)) + return uitoa(uint(zone)) } func zoneToInt(zone string) int { diff --git a/libgo/go/net/ipsock_plan9.go b/libgo/go/net/ipsock_plan9.go index 94ceea31b03..9da6ec3053b 100644 --- a/libgo/go/net/ipsock_plan9.go +++ b/libgo/go/net/ipsock_plan9.go @@ -7,7 +7,6 @@ package net import ( - "errors" "os" "syscall" ) @@ -60,15 +59,15 @@ func parsePlan9Addr(s string) (ip IP, iport int, err error) { if i >= 0 { addr = ParseIP(s[:i]) if addr == nil { - return nil, 0, errors.New("parsing IP failed") + return nil, 0, &ParseError{Type: "IP address", Text: s} } } p, _, ok := dtoi(s[i+1:], 0) if !ok { - return nil, 0, errors.New("parsing port failed") + return nil, 0, &ParseError{Type: "port", Text: s} } if p < 0 || p > 0xFFFF { - return nil, 0, &AddrError{"invalid port", string(p)} + return nil, 0, &AddrError{Err: "invalid port", Addr: string(p)} } return addr, p, nil } @@ -95,7 +94,7 @@ func readPlan9Addr(proto, filename string) (addr Addr, err error) { case "udp": addr = &UDPAddr{IP: ip, Port: port} default: - return nil, errors.New("unknown protocol " + proto) + return nil, UnknownNetworkError(proto) } return addr, nil } @@ -141,6 +140,24 @@ func netErr(e error) { if !ok { return } + nonNilInterface := func(a Addr) bool { + switch a := a.(type) { + case *TCPAddr: + return a == nil + case *UDPAddr: + return a == nil + case *IPAddr: + return a == nil + default: + return false + } + } + if nonNilInterface(oe.Source) { + oe.Source = nil + } + if nonNilInterface(oe.Addr) { + oe.Addr = nil + } if pe, ok := oe.Err.(*os.PathError); ok { if _, ok = pe.Err.(syscall.ErrorString); ok { oe.Err = pe.Err @@ -152,23 +169,23 @@ func dialPlan9(net string, laddr, raddr Addr) (fd *netFD, err error) { defer func() { netErr(err) }() f, dest, proto, name, err := startPlan9(net, raddr) if err != nil { - return nil, &OpError{"dial", net, raddr, err} + return nil, &OpError{Op: "dial", Net: net, Source: laddr, Addr: raddr, Err: err} } _, err = f.WriteString("connect " + dest) if err != nil { f.Close() - return nil, &OpError{"dial", f.Name(), raddr, err} + return nil, &OpError{Op: "dial", Net: f.Name(), Source: laddr, Addr: raddr, Err: err} } data, err := os.OpenFile(netdir+"/"+proto+"/"+name+"/data", os.O_RDWR, 0) if err != nil { f.Close() - return nil, &OpError{"dial", net, raddr, err} + return nil, &OpError{Op: "dial", Net: net, Source: laddr, Addr: raddr, Err: err} } laddr, err = readPlan9Addr(proto, netdir+"/"+proto+"/"+name+"/local") if err != nil { data.Close() f.Close() - return nil, &OpError{"dial", proto, raddr, err} + return nil, &OpError{Op: "dial", Net: proto, Source: laddr, Addr: raddr, Err: err} } return newFD(proto, name, f, data, laddr, raddr) } @@ -177,52 +194,52 @@ func listenPlan9(net string, laddr Addr) (fd *netFD, err error) { defer func() { netErr(err) }() f, dest, proto, name, err := startPlan9(net, laddr) if err != nil { - return nil, &OpError{"listen", net, laddr, err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err} } _, err = f.WriteString("announce " + dest) if err != nil { f.Close() - return nil, &OpError{"announce", proto, laddr, err} + return nil, &OpError{Op: "announce", Net: proto, Source: nil, Addr: laddr, Err: err} } laddr, err = readPlan9Addr(proto, netdir+"/"+proto+"/"+name+"/local") if err != nil { f.Close() - return nil, &OpError{Op: "listen", Net: net, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err} } return newFD(proto, name, f, nil, laddr, nil) } -func (l *netFD) netFD() (*netFD, error) { - return newFD(l.proto, l.n, l.ctl, l.data, l.laddr, l.raddr) +func (fd *netFD) netFD() (*netFD, error) { + return newFD(fd.net, fd.n, fd.ctl, fd.data, fd.laddr, fd.raddr) } -func (l *netFD) acceptPlan9() (fd *netFD, err error) { +func (fd *netFD) acceptPlan9() (nfd *netFD, err error) { defer func() { netErr(err) }() - if err := l.readLock(); err != nil { + if err := fd.readLock(); err != nil { return nil, err } - defer l.readUnlock() - f, err := os.Open(l.dir + "/listen") + defer fd.readUnlock() + f, err := os.Open(fd.dir + "/listen") if err != nil { - return nil, &OpError{"accept", l.dir + "/listen", l.laddr, err} + return nil, &OpError{Op: "accept", Net: fd.dir + "/listen", Source: nil, Addr: fd.laddr, Err: err} } var buf [16]byte n, err := f.Read(buf[:]) if err != nil { f.Close() - return nil, &OpError{"accept", l.dir + "/listen", l.laddr, err} + return nil, &OpError{Op: "accept", Net: fd.dir + "/listen", Source: nil, Addr: fd.laddr, Err: err} } name := string(buf[:n]) - data, err := os.OpenFile(netdir+"/"+l.proto+"/"+name+"/data", os.O_RDWR, 0) + data, err := os.OpenFile(netdir+"/"+fd.net+"/"+name+"/data", os.O_RDWR, 0) if err != nil { f.Close() - return nil, &OpError{"accept", l.proto, l.laddr, err} + return nil, &OpError{Op: "accept", Net: fd.net, Source: nil, Addr: fd.laddr, Err: err} } - raddr, err := readPlan9Addr(l.proto, netdir+"/"+l.proto+"/"+name+"/remote") + raddr, err := readPlan9Addr(fd.net, netdir+"/"+fd.net+"/"+name+"/remote") if err != nil { data.Close() f.Close() - return nil, &OpError{"accept", l.proto, l.laddr, err} + return nil, &OpError{Op: "accept", Net: fd.net, Source: nil, Addr: fd.laddr, Err: err} } - return newFD(l.proto, name, f, data, l.laddr, raddr) + return newFD(fd.net, name, f, data, fd.laddr, raddr) } diff --git a/libgo/go/net/ipsock_posix.go b/libgo/go/net/ipsock_posix.go index f9ebe40a21e..83eaf855b4c 100644 --- a/libgo/go/net/ipsock_posix.go +++ b/libgo/go/net/ipsock_posix.go @@ -9,17 +9,25 @@ package net import ( + "runtime" "syscall" "time" ) +// BUG(rsc,mikio): On DragonFly BSD and OpenBSD, listening on the +// "tcp" and "udp" networks does not listen for both IPv4 and IPv6 +// connections. This is due to the fact that IPv4 traffic will not be +// routed to an IPv6 socket - two separate sockets are required if +// both address families are to be supported. +// See inet6(4) for details. + func probeIPv4Stack() bool { - s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) + s, err := socketFunc(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) switch err { case syscall.EAFNOSUPPORT, syscall.EPROTONOSUPPORT: return false case nil: - closesocket(s) + closeFunc(s) } return true } @@ -41,20 +49,35 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) { var probes = []struct { laddr TCPAddr value int - ok bool }{ // IPv6 communication capability {laddr: TCPAddr{IP: ParseIP("::1")}, value: 1}, // IPv6 IPv4-mapped address communication capability {laddr: TCPAddr{IP: IPv4(127, 0, 0, 1)}, value: 0}, } + var supps [2]bool + switch runtime.GOOS { + case "dragonfly", "openbsd": + // Some released versions of DragonFly BSD pretend to + // accept IPV6_V6ONLY=0 successfully, but the state + // still stays IPV6_V6ONLY=1. Eventually DragonFly BSD + // stops preteding, but the transition period would + // cause unpredictable behavior and we need to avoid + // it. + // + // OpenBSD also doesn't support IPV6_V6ONLY=0 but it + // never pretends to accept IPV6_V6OLY=0. It always + // returns an error and we don't need to probe the + // capability. + probes = probes[:1] + } for i := range probes { - s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) + s, err := socketFunc(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) if err != nil { continue } - defer closesocket(s) + defer closeFunc(s) syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, probes[i].value) sa, err := probes[i].laddr.sockaddr(syscall.AF_INET6) if err != nil { @@ -63,10 +86,10 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) { if err := syscall.Bind(s, sa); err != nil { continue } - probes[i].ok = true + supps[i] = true } - return probes[0].ok, probes[1].ok + return supps[0], supps[1] } // favoriteAddrFamily returns the appropriate address family to @@ -144,7 +167,7 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e ip = IPv4zero } if ip = ip.To4(); ip == nil { - return nil, InvalidAddrError("non-IPv4 address") + return nil, &AddrError{Err: "non-IPv4 address", Addr: ip.String()} } sa := new(syscall.SockaddrInet4) for i := 0; i < IPv4len; i++ { @@ -163,7 +186,7 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e ip = IPv6zero } if ip = ip.To16(); ip == nil { - return nil, InvalidAddrError("non-IPv6 address") + return nil, &AddrError{Err: "non-IPv6 address", Addr: ip.String()} } sa := new(syscall.SockaddrInet6) for i := 0; i < IPv6len; i++ { @@ -173,5 +196,5 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e sa.ZoneId = uint32(zoneToInt(zone)) return sa, nil } - return nil, InvalidAddrError("unexpected socket family") + return nil, &AddrError{Err: "invalid address family", Addr: ip.String()} } diff --git a/libgo/go/net/ipsock_test.go b/libgo/go/net/ipsock_test.go index 9ecaaec69f6..b36557a1575 100644 --- a/libgo/go/net/ipsock_test.go +++ b/libgo/go/net/ipsock_test.go @@ -9,185 +9,274 @@ import ( "testing" ) -var testInetaddr = func(ip IP) netaddr { return &TCPAddr{IP: ip, Port: 5682} } +var testInetaddr = func(ip IPAddr) Addr { return &TCPAddr{IP: ip.IP, Port: 5682, Zone: ip.Zone} } -var firstFavoriteAddrTests = []struct { - filter func(IP) IP - ips []IP - inetaddr func(IP) netaddr - addr netaddr - err error +var addrListTests = []struct { + filter func(IPAddr) bool + ips []IPAddr + inetaddr func(IPAddr) Addr + first Addr + primaries addrList + fallbacks addrList + err error }{ { nil, - []IP{ - IPv4(127, 0, 0, 1), - IPv6loopback, + []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv6loopback}, }, testInetaddr, - addrList{ - &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, - &TCPAddr{IP: IPv6loopback, Port: 5682}, - }, + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + addrList{&TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}}, + addrList{&TCPAddr{IP: IPv6loopback, Port: 5682}}, nil, }, { nil, - []IP{ - IPv6loopback, - IPv4(127, 0, 0, 1), + []IPAddr{ + {IP: IPv6loopback}, + {IP: IPv4(127, 0, 0, 1)}, }, testInetaddr, - addrList{ - &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, - &TCPAddr{IP: IPv6loopback, Port: 5682}, - }, + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + addrList{&TCPAddr{IP: IPv6loopback, Port: 5682}}, + addrList{&TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}}, nil, }, { nil, - []IP{ - IPv4(127, 0, 0, 1), - IPv4(192, 168, 0, 1), + []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv4(192, 168, 0, 1)}, }, testInetaddr, &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + addrList{ + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + &TCPAddr{IP: IPv4(192, 168, 0, 1), Port: 5682}, + }, + nil, nil, }, { nil, - []IP{ - IPv6loopback, - ParseIP("fe80::1"), + []IPAddr{ + {IP: IPv6loopback}, + {IP: ParseIP("fe80::1"), Zone: "eth0"}, }, testInetaddr, &TCPAddr{IP: IPv6loopback, Port: 5682}, + addrList{ + &TCPAddr{IP: IPv6loopback, Port: 5682}, + &TCPAddr{IP: ParseIP("fe80::1"), Port: 5682, Zone: "eth0"}, + }, + nil, nil, }, { nil, - []IP{ - IPv4(127, 0, 0, 1), - IPv4(192, 168, 0, 1), - IPv6loopback, - ParseIP("fe80::1"), + []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv4(192, 168, 0, 1)}, + {IP: IPv6loopback}, + {IP: ParseIP("fe80::1"), Zone: "eth0"}, }, testInetaddr, + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, addrList{ &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + &TCPAddr{IP: IPv4(192, 168, 0, 1), Port: 5682}, + }, + addrList{ &TCPAddr{IP: IPv6loopback, Port: 5682}, + &TCPAddr{IP: ParseIP("fe80::1"), Port: 5682, Zone: "eth0"}, }, nil, }, { nil, - []IP{ - IPv6loopback, - ParseIP("fe80::1"), - IPv4(127, 0, 0, 1), - IPv4(192, 168, 0, 1), + []IPAddr{ + {IP: IPv6loopback}, + {IP: ParseIP("fe80::1"), Zone: "eth0"}, + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv4(192, 168, 0, 1)}, }, testInetaddr, + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, addrList{ - &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, &TCPAddr{IP: IPv6loopback, Port: 5682}, + &TCPAddr{IP: ParseIP("fe80::1"), Port: 5682, Zone: "eth0"}, + }, + addrList{ + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + &TCPAddr{IP: IPv4(192, 168, 0, 1), Port: 5682}, }, nil, }, { nil, - []IP{ - IPv4(127, 0, 0, 1), - IPv6loopback, - IPv4(192, 168, 0, 1), - ParseIP("fe80::1"), + []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv6loopback}, + {IP: IPv4(192, 168, 0, 1)}, + {IP: ParseIP("fe80::1"), Zone: "eth0"}, }, testInetaddr, + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, addrList{ &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + &TCPAddr{IP: IPv4(192, 168, 0, 1), Port: 5682}, + }, + addrList{ &TCPAddr{IP: IPv6loopback, Port: 5682}, + &TCPAddr{IP: ParseIP("fe80::1"), Port: 5682, Zone: "eth0"}, }, nil, }, { nil, - []IP{ - IPv6loopback, - IPv4(127, 0, 0, 1), - ParseIP("fe80::1"), - IPv4(192, 168, 0, 1), + []IPAddr{ + {IP: IPv6loopback}, + {IP: IPv4(127, 0, 0, 1)}, + {IP: ParseIP("fe80::1"), Zone: "eth0"}, + {IP: IPv4(192, 168, 0, 1)}, }, testInetaddr, + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, addrList{ - &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, &TCPAddr{IP: IPv6loopback, Port: 5682}, + &TCPAddr{IP: ParseIP("fe80::1"), Port: 5682, Zone: "eth0"}, + }, + addrList{ + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + &TCPAddr{IP: IPv4(192, 168, 0, 1), Port: 5682}, }, nil, }, { ipv4only, - []IP{ - IPv4(127, 0, 0, 1), - IPv6loopback, + []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv6loopback}, }, testInetaddr, &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + addrList{&TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}}, + nil, nil, }, { ipv4only, - []IP{ - IPv6loopback, - IPv4(127, 0, 0, 1), + []IPAddr{ + {IP: IPv6loopback}, + {IP: IPv4(127, 0, 0, 1)}, }, testInetaddr, &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}, + addrList{&TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5682}}, + nil, nil, }, { ipv6only, - []IP{ - IPv4(127, 0, 0, 1), - IPv6loopback, + []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv6loopback}, }, testInetaddr, &TCPAddr{IP: IPv6loopback, Port: 5682}, + addrList{&TCPAddr{IP: IPv6loopback, Port: 5682}}, + nil, nil, }, { ipv6only, - []IP{ - IPv6loopback, - IPv4(127, 0, 0, 1), + []IPAddr{ + {IP: IPv6loopback}, + {IP: IPv4(127, 0, 0, 1)}, }, testInetaddr, &TCPAddr{IP: IPv6loopback, Port: 5682}, + addrList{&TCPAddr{IP: IPv6loopback, Port: 5682}}, + nil, nil, }, - {nil, nil, testInetaddr, nil, errNoSuitableAddress}, + {nil, nil, testInetaddr, nil, nil, nil, errNoSuitableAddress}, - {ipv4only, nil, testInetaddr, nil, errNoSuitableAddress}, - {ipv4only, []IP{IPv6loopback}, testInetaddr, nil, errNoSuitableAddress}, + {ipv4only, nil, testInetaddr, nil, nil, nil, errNoSuitableAddress}, + {ipv4only, []IPAddr{{IP: IPv6loopback}}, testInetaddr, nil, nil, nil, errNoSuitableAddress}, - {ipv6only, nil, testInetaddr, nil, errNoSuitableAddress}, - {ipv6only, []IP{IPv4(127, 0, 0, 1)}, testInetaddr, nil, errNoSuitableAddress}, + {ipv6only, nil, testInetaddr, nil, nil, nil, errNoSuitableAddress}, + {ipv6only, []IPAddr{{IP: IPv4(127, 0, 0, 1)}}, testInetaddr, nil, nil, nil, errNoSuitableAddress}, } -func TestFirstFavoriteAddr(t *testing.T) { +func TestAddrList(t *testing.T) { if !supportsIPv4 || !supportsIPv6 { - t.Skip("ipv4 or ipv6 is not supported") + t.Skip("both IPv4 and IPv6 are required") } - for i, tt := range firstFavoriteAddrTests { - addr, err := firstFavoriteAddr(tt.filter, tt.ips, tt.inetaddr) + for i, tt := range addrListTests { + addrs, err := filterAddrList(tt.filter, tt.ips, tt.inetaddr) if err != tt.err { - t.Errorf("#%v: got %v; expected %v", i, err, tt.err) + t.Errorf("#%v: got %v; want %v", i, err, tt.err) + } + if tt.err != nil { + if len(addrs) != 0 { + t.Errorf("#%v: got %v; want 0", i, len(addrs)) + } + continue + } + first := addrs.first(isIPv4) + if !reflect.DeepEqual(first, tt.first) { + t.Errorf("#%v: got %v; want %v", i, first, tt.first) + } + primaries, fallbacks := addrs.partition(isIPv4) + if !reflect.DeepEqual(primaries, tt.primaries) { + t.Errorf("#%v: got %v; want %v", i, primaries, tt.primaries) + } + if !reflect.DeepEqual(fallbacks, tt.fallbacks) { + t.Errorf("#%v: got %v; want %v", i, fallbacks, tt.fallbacks) } - if !reflect.DeepEqual(addr, tt.addr) { - t.Errorf("#%v: got %v; expected %v", i, addr, tt.addr) + expectedLen := len(primaries) + len(fallbacks) + if len(addrs) != expectedLen { + t.Errorf("#%v: got %v; want %v", i, len(addrs), expectedLen) + } + } +} + +func TestAddrListPartition(t *testing.T) { + addrs := addrList{ + &IPAddr{IP: ParseIP("fe80::"), Zone: "eth0"}, + &IPAddr{IP: ParseIP("fe80::1"), Zone: "eth0"}, + &IPAddr{IP: ParseIP("fe80::2"), Zone: "eth0"}, + } + cases := []struct { + lastByte byte + primaries addrList + fallbacks addrList + }{ + {0, addrList{addrs[0]}, addrList{addrs[1], addrs[2]}}, + {1, addrList{addrs[0], addrs[2]}, addrList{addrs[1]}}, + {2, addrList{addrs[0], addrs[1]}, addrList{addrs[2]}}, + {3, addrList{addrs[0], addrs[1], addrs[2]}, nil}, + } + for i, tt := range cases { + // Inverting the function's output should not affect the outcome. + for _, invert := range []bool{false, true} { + primaries, fallbacks := addrs.partition(func(a Addr) bool { + ip := a.(*IPAddr).IP + return (ip[len(ip)-1] == tt.lastByte) != invert + }) + if !reflect.DeepEqual(primaries, tt.primaries) { + t.Errorf("#%v: got %v; want %v", i, primaries, tt.primaries) + } + if !reflect.DeepEqual(fallbacks, tt.fallbacks) { + t.Errorf("#%v: got %v; want %v", i, fallbacks, tt.fallbacks) + } } } } diff --git a/libgo/go/net/listen_test.go b/libgo/go/net/listen_test.go new file mode 100644 index 00000000000..d5627f2556d --- /dev/null +++ b/libgo/go/net/listen_test.go @@ -0,0 +1,685 @@ +// Copyright 2011 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 !plan9 + +package net + +import ( + "fmt" + "os" + "runtime" + "syscall" + "testing" +) + +func (ln *TCPListener) port() string { + _, port, err := SplitHostPort(ln.Addr().String()) + if err != nil { + return "" + } + return port +} + +func (c *UDPConn) port() string { + _, port, err := SplitHostPort(c.LocalAddr().String()) + if err != nil { + return "" + } + return port +} + +var tcpListenerTests = []struct { + network string + address string +}{ + {"tcp", ""}, + {"tcp", "0.0.0.0"}, + {"tcp", "::ffff:0.0.0.0"}, + {"tcp", "::"}, + + {"tcp", "127.0.0.1"}, + {"tcp", "::ffff:127.0.0.1"}, + {"tcp", "::1"}, + + {"tcp4", ""}, + {"tcp4", "0.0.0.0"}, + {"tcp4", "::ffff:0.0.0.0"}, + + {"tcp4", "127.0.0.1"}, + {"tcp4", "::ffff:127.0.0.1"}, + + {"tcp6", ""}, + {"tcp6", "::"}, + + {"tcp6", "::1"}, +} + +// TestTCPListener tests both single and double listen to a test +// listener with same address family, same listening address and +// same port. +func TestTCPListener(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + + for _, tt := range tcpListenerTests { + if !testableListenArgs(tt.network, JoinHostPort(tt.address, "0"), "") { + t.Logf("skipping %s test", tt.network+" "+tt.address) + continue + } + + ln1, err := Listen(tt.network, JoinHostPort(tt.address, "0")) + if err != nil { + t.Fatal(err) + } + if err := checkFirstListener(tt.network, ln1); err != nil { + ln1.Close() + t.Fatal(err) + } + ln2, err := Listen(tt.network, JoinHostPort(tt.address, ln1.(*TCPListener).port())) + if err == nil { + ln2.Close() + } + if err := checkSecondListener(tt.network, tt.address, err); err != nil { + ln1.Close() + t.Fatal(err) + } + ln1.Close() + } +} + +var udpListenerTests = []struct { + network string + address string +}{ + {"udp", ""}, + {"udp", "0.0.0.0"}, + {"udp", "::ffff:0.0.0.0"}, + {"udp", "::"}, + + {"udp", "127.0.0.1"}, + {"udp", "::ffff:127.0.0.1"}, + {"udp", "::1"}, + + {"udp4", ""}, + {"udp4", "0.0.0.0"}, + {"udp4", "::ffff:0.0.0.0"}, + + {"udp4", "127.0.0.1"}, + {"udp4", "::ffff:127.0.0.1"}, + + {"udp6", ""}, + {"udp6", "::"}, + + {"udp6", "::1"}, +} + +// TestUDPListener tests both single and double listen to a test +// listener with same address family, same listening address and +// same port. +func TestUDPListener(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + + for _, tt := range udpListenerTests { + if !testableListenArgs(tt.network, JoinHostPort(tt.address, "0"), "") { + t.Logf("skipping %s test", tt.network+" "+tt.address) + continue + } + + c1, err := ListenPacket(tt.network, JoinHostPort(tt.address, "0")) + if err != nil { + t.Fatal(err) + } + if err := checkFirstListener(tt.network, c1); err != nil { + c1.Close() + t.Fatal(err) + } + c2, err := ListenPacket(tt.network, JoinHostPort(tt.address, c1.(*UDPConn).port())) + if err == nil { + c2.Close() + } + if err := checkSecondListener(tt.network, tt.address, err); err != nil { + c1.Close() + t.Fatal(err) + } + c1.Close() + } +} + +var dualStackTCPListenerTests = []struct { + network1, address1 string // first listener + network2, address2 string // second listener + xerr error // expected error value, nil or other +}{ + // 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" "[::]" - - - ok + // "tcp" "[::]" "tcp" "" - - - ok + // "tcp" "0.0.0.0" "tcp" "[::]" - - - ok + // "tcp" "[::]" "tcp" "0.0.0.0" - - - ok + // "tcp" "[::ffff:0.0.0.0]" "tcp" "[::]" - - - ok + // "tcp" "[::]" "tcp" "[::ffff:0.0.0.0]" - - - ok + // ------------------------------------------------------------------------------------ + // "tcp4" "" "tcp6" "" ok ok ok ok + // "tcp6" "" "tcp4" "" ok ok ok ok + // "tcp4" "0.0.0.0" "tcp6" "[::]" ok ok ok ok + // "tcp6" "[::]" "tcp4" "0.0.0.0" ok ok ok ok + // ------------------------------------------------------------------------------------ + // "tcp" "127.0.0.1" "tcp" "[::1]" ok ok ok ok + // "tcp" "[::1]" "tcp" "127.0.0.1" ok ok ok ok + // "tcp4" "127.0.0.1" "tcp6" "[::1]" ok ok ok ok + // "tcp6" "[::1]" "tcp4" "127.0.0.1" ok ok ok ok + // + // Platform default configurations: + // darwin, kernel version 11.3.0 + // net.inet6.ip6.v6only=0 (overridable by sysctl or IPV6_V6ONLY option) + // freebsd, kernel version 8.2 + // net.inet6.ip6.v6only=1 (overridable by sysctl or IPV6_V6ONLY option) + // linux, kernel version 3.0.0 + // net.ipv6.bindv6only=0 (overridable by sysctl or IPV6_V6ONLY option) + // openbsd, kernel version 5.0 + // net.inet6.ip6.v6only=1 (overriding is prohibited) + + {"tcp", "", "tcp", "", syscall.EADDRINUSE}, + {"tcp", "", "tcp", "0.0.0.0", syscall.EADDRINUSE}, + {"tcp", "0.0.0.0", "tcp", "", syscall.EADDRINUSE}, + + {"tcp", "", "tcp", "::", syscall.EADDRINUSE}, + {"tcp", "::", "tcp", "", syscall.EADDRINUSE}, + {"tcp", "0.0.0.0", "tcp", "::", syscall.EADDRINUSE}, + {"tcp", "::", "tcp", "0.0.0.0", syscall.EADDRINUSE}, + {"tcp", "::ffff:0.0.0.0", "tcp", "::", syscall.EADDRINUSE}, + {"tcp", "::", "tcp", "::ffff:0.0.0.0", syscall.EADDRINUSE}, + + {"tcp4", "", "tcp6", "", nil}, + {"tcp6", "", "tcp4", "", nil}, + {"tcp4", "0.0.0.0", "tcp6", "::", nil}, + {"tcp6", "::", "tcp4", "0.0.0.0", nil}, + + {"tcp", "127.0.0.1", "tcp", "::1", nil}, + {"tcp", "::1", "tcp", "127.0.0.1", nil}, + {"tcp4", "127.0.0.1", "tcp6", "::1", nil}, + {"tcp6", "::1", "tcp4", "127.0.0.1", nil}, +} + +// TestDualStackTCPListener tests both single and double listen +// to a test listener with various address families, different +// listening address and same port. +func TestDualStackTCPListener(t *testing.T) { + switch runtime.GOOS { + case "dragonfly", "nacl", "plan9": // re-enable on dragonfly once the new IP control block management has landed + t.Skipf("not supported on %s", runtime.GOOS) + } + if !supportsIPv4 || !supportsIPv6 { + t.Skip("both IPv4 and IPv6 are required") + } + + for _, tt := range dualStackTCPListenerTests { + if !testableListenArgs(tt.network1, JoinHostPort(tt.address1, "0"), "") { + t.Logf("skipping %s test", tt.network1+" "+tt.address1) + continue + } + + if !supportsIPv4map && differentWildcardAddr(tt.address1, tt.address2) { + tt.xerr = nil + } + var firstErr, secondErr error + for i := 0; i < 5; i++ { + lns, err := newDualStackListener() + if err != nil { + t.Fatal(err) + } + port := lns[0].port() + for _, ln := range lns { + ln.Close() + } + var ln1 Listener + ln1, firstErr = Listen(tt.network1, JoinHostPort(tt.address1, port)) + if firstErr != nil { + continue + } + if err := checkFirstListener(tt.network1, ln1); err != nil { + ln1.Close() + t.Fatal(err) + } + ln2, err := Listen(tt.network2, JoinHostPort(tt.address2, ln1.(*TCPListener).port())) + if err == nil { + ln2.Close() + } + if secondErr = checkDualStackSecondListener(tt.network2, tt.address2, err, tt.xerr); secondErr != nil { + ln1.Close() + continue + } + ln1.Close() + break + } + if firstErr != nil { + t.Error(firstErr) + } + if secondErr != nil { + t.Error(secondErr) + } + } +} + +var dualStackUDPListenerTests = []struct { + network1, address1 string // first listener + network2, address2 string // second listener + xerr error // expected error value, nil or other +}{ + {"udp", "", "udp", "", syscall.EADDRINUSE}, + {"udp", "", "udp", "0.0.0.0", syscall.EADDRINUSE}, + {"udp", "0.0.0.0", "udp", "", syscall.EADDRINUSE}, + + {"udp", "", "udp", "::", syscall.EADDRINUSE}, + {"udp", "::", "udp", "", syscall.EADDRINUSE}, + {"udp", "0.0.0.0", "udp", "::", syscall.EADDRINUSE}, + {"udp", "::", "udp", "0.0.0.0", syscall.EADDRINUSE}, + {"udp", "::ffff:0.0.0.0", "udp", "::", syscall.EADDRINUSE}, + {"udp", "::", "udp", "::ffff:0.0.0.0", syscall.EADDRINUSE}, + + {"udp4", "", "udp6", "", nil}, + {"udp6", "", "udp4", "", nil}, + {"udp4", "0.0.0.0", "udp6", "::", nil}, + {"udp6", "::", "udp4", "0.0.0.0", nil}, + + {"udp", "127.0.0.1", "udp", "::1", nil}, + {"udp", "::1", "udp", "127.0.0.1", nil}, + {"udp4", "127.0.0.1", "udp6", "::1", nil}, + {"udp6", "::1", "udp4", "127.0.0.1", nil}, +} + +// TestDualStackUDPListener tests both single and double listen +// to a test listener with various address families, differnet +// listening address and same port. +func TestDualStackUDPListener(t *testing.T) { + switch runtime.GOOS { + case "dragonfly", "nacl", "plan9": // re-enable on dragonfly once the new IP control block management has landed + t.Skipf("not supported on %s", runtime.GOOS) + } + if !supportsIPv4 || !supportsIPv6 { + t.Skip("both IPv4 and IPv6 are required") + } + + for _, tt := range dualStackUDPListenerTests { + if !testableListenArgs(tt.network1, JoinHostPort(tt.address1, "0"), "") { + t.Logf("skipping %s test", tt.network1+" "+tt.address1) + continue + } + + if !supportsIPv4map && differentWildcardAddr(tt.address1, tt.address2) { + tt.xerr = nil + } + var firstErr, secondErr error + for i := 0; i < 5; i++ { + cs, err := newDualStackPacketListener() + if err != nil { + t.Fatal(err) + } + port := cs[0].port() + for _, c := range cs { + c.Close() + } + var c1 PacketConn + c1, firstErr = ListenPacket(tt.network1, JoinHostPort(tt.address1, port)) + if firstErr != nil { + continue + } + if err := checkFirstListener(tt.network1, c1); err != nil { + c1.Close() + t.Fatal(err) + } + c2, err := ListenPacket(tt.network2, JoinHostPort(tt.address2, c1.(*UDPConn).port())) + if err == nil { + c2.Close() + } + if secondErr = checkDualStackSecondListener(tt.network2, tt.address2, err, tt.xerr); secondErr != nil { + c1.Close() + continue + } + c1.Close() + break + } + if firstErr != nil { + t.Error(firstErr) + } + if secondErr != nil { + t.Error(secondErr) + } + } +} + +func differentWildcardAddr(i, j string) bool { + if (i == "" || i == "0.0.0.0" || i == "::ffff:0.0.0.0") && (j == "" || j == "0.0.0.0" || j == "::ffff:0.0.0.0") { + return false + } + if i == "[::]" && j == "[::]" { + return false + } + return true +} + +func checkFirstListener(network string, ln interface{}) error { + switch network { + case "tcp": + fd := ln.(*TCPListener).fd + if err := checkDualStackAddrFamily(fd); err != nil { + return err + } + case "tcp4": + fd := ln.(*TCPListener).fd + if fd.family != syscall.AF_INET { + return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET) + } + case "tcp6": + fd := ln.(*TCPListener).fd + if fd.family != syscall.AF_INET6 { + return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET6) + } + case "udp": + fd := ln.(*UDPConn).fd + if err := checkDualStackAddrFamily(fd); err != nil { + return err + } + case "udp4": + fd := ln.(*UDPConn).fd + if fd.family != syscall.AF_INET { + return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET) + } + case "udp6": + fd := ln.(*UDPConn).fd + if fd.family != syscall.AF_INET6 { + return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET6) + } + default: + return UnknownNetworkError(network) + } + return nil +} + +func checkSecondListener(network, address string, err error) error { + switch network { + case "tcp", "tcp4", "tcp6": + if err == nil { + return fmt.Errorf("%s should fail", network+" "+address) + } + case "udp", "udp4", "udp6": + if err == nil { + return fmt.Errorf("%s should fail", network+" "+address) + } + default: + return UnknownNetworkError(network) + } + return nil +} + +func checkDualStackSecondListener(network, address string, err, xerr error) error { + switch network { + case "tcp", "tcp4", "tcp6": + if xerr == nil && err != nil || xerr != nil && err == nil { + return fmt.Errorf("%s got %v; want %v", network+" "+address, err, xerr) + } + case "udp", "udp4", "udp6": + if xerr == nil && err != nil || xerr != nil && err == nil { + return fmt.Errorf("%s got %v; want %v", network+" "+address, err, xerr) + } + default: + return UnknownNetworkError(network) + } + return nil +} + +func checkDualStackAddrFamily(fd *netFD) error { + switch a := fd.laddr.(type) { + case *TCPAddr: + // If a node under test supports both IPv6 capability + // and IPv6 IPv4-mapping capability, we can assume + // that the node listens on a wildcard address with an + // AF_INET6 socket. + if supportsIPv4map && fd.laddr.(*TCPAddr).isWildcard() { + if fd.family != syscall.AF_INET6 { + return fmt.Errorf("Listen(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, syscall.AF_INET6) + } + } else { + if fd.family != a.family() { + return fmt.Errorf("Listen(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, a.family()) + } + } + case *UDPAddr: + // If a node under test supports both IPv6 capability + // and IPv6 IPv4-mapping capability, we can assume + // that the node listens on a wildcard address with an + // AF_INET6 socket. + if supportsIPv4map && fd.laddr.(*UDPAddr).isWildcard() { + if fd.family != syscall.AF_INET6 { + return fmt.Errorf("ListenPacket(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, syscall.AF_INET6) + } + } else { + if fd.family != a.family() { + return fmt.Errorf("ListenPacket(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, a.family()) + } + } + default: + return fmt.Errorf("unexpected protocol address type: %T", a) + } + return nil +} + +func TestWildWildcardListener(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + if testing.Short() || !*testExternal { + t.Skip("avoid external network") + } + + defer func() { + if p := recover(); p != nil { + t.Fatalf("panicked: %v", p) + } + }() + + if ln, err := Listen("tcp", ""); err == nil { + ln.Close() + } + if ln, err := ListenPacket("udp", ""); err == nil { + ln.Close() + } + if ln, err := ListenTCP("tcp", nil); err == nil { + ln.Close() + } + if ln, err := ListenUDP("udp", nil); err == nil { + ln.Close() + } + if ln, err := ListenIP("ip:icmp", nil); err == nil { + ln.Close() + } +} + +var ipv4MulticastListenerTests = []struct { + net string + gaddr *UDPAddr // see RFC 4727 +}{ + {"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}}, + + {"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}}, +} + +// TestIPv4MulticastListener tests both single and double listen to a +// test listener with same address family, same group address and same +// port. +func TestIPv4MulticastListener(t *testing.T) { + switch runtime.GOOS { + case "android", "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + case "solaris": + t.Skipf("not supported on solaris, see golang.org/issue/7399") + } + + closer := func(cs []*UDPConn) { + for _, c := range cs { + if c != nil { + c.Close() + } + } + } + + for _, ifi := range []*Interface{loopbackInterface(), nil} { + // Note that multicast interface assignment by system + // is not recommended because it usually relies on + // routing stuff for finding out an appropriate + // nexthop containing both network and link layer + // adjacencies. + if ifi == nil && !*testExternal { + continue + } + for _, tt := range ipv4MulticastListenerTests { + var err error + cs := make([]*UDPConn, 2) + if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { + t.Fatal(err) + } + if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil { + closer(cs) + t.Fatal(err) + } + if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { + closer(cs) + t.Fatal(err) + } + if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil { + closer(cs) + t.Fatal(err) + } + closer(cs) + } + } +} + +var ipv6MulticastListenerTests = []struct { + net string + gaddr *UDPAddr // see RFC 4727 +}{ + {"udp", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}}, + {"udp", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}}, + {"udp", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}}, + {"udp", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}}, + {"udp", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}}, + {"udp", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}}, + + {"udp6", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}}, + {"udp6", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}}, + {"udp6", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}}, + {"udp6", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}}, + {"udp6", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}}, + {"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}}, +} + +// TestIPv6MulticastListener tests both single and double listen to a +// test listener with same address family, same group address and same +// port. +func TestIPv6MulticastListener(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + case "solaris": + t.Skipf("not supported on solaris, see issue 7399") + } + if !supportsIPv6 { + t.Skip("ipv6 is not supported") + } + if os.Getuid() != 0 { + t.Skip("must be root") + } + + closer := func(cs []*UDPConn) { + for _, c := range cs { + if c != nil { + c.Close() + } + } + } + + for _, ifi := range []*Interface{loopbackInterface(), nil} { + // Note that multicast interface assignment by system + // is not recommended because it usually relies on + // routing stuff for finding out an appropriate + // nexthop containing both network and link layer + // adjacencies. + if ifi == nil && (!*testExternal || !*testIPv6) { + continue + } + for _, tt := range ipv6MulticastListenerTests { + var err error + cs := make([]*UDPConn, 2) + if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { + t.Fatal(err) + } + if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil { + closer(cs) + t.Fatal(err) + } + if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { + closer(cs) + t.Fatal(err) + } + if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil { + closer(cs) + t.Fatal(err) + } + closer(cs) + } + } +} + +func checkMulticastListener(c *UDPConn, ip IP) error { + if ok, err := multicastRIBContains(ip); err != nil { + return err + } else if !ok { + return fmt.Errorf("%s not found in multicast rib", ip.String()) + } + la := c.LocalAddr() + if la, ok := la.(*UDPAddr); !ok || la.Port == 0 { + return fmt.Errorf("got %v; want a proper address with non-zero port number", la) + } + return nil +} + +func multicastRIBContains(ip IP) (bool, error) { + switch runtime.GOOS { + case "dragonfly", "netbsd", "openbsd", "plan9", "solaris", "windows": + return true, nil // not implemented yet + case "linux": + if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" { + return true, nil // not implemented yet + } + } + ift, err := Interfaces() + if err != nil { + return false, err + } + for _, ifi := range ift { + ifmat, err := ifi.MulticastAddrs() + if err != nil { + return false, err + } + for _, ifma := range ifmat { + if ifma.(*IPAddr).IP.Equal(ip) { + return true, nil + } + } + } + return false, nil +} diff --git a/libgo/go/net/lookup.go b/libgo/go/net/lookup.go index aeffe6c9b72..a7ceee823f1 100644 --- a/libgo/go/net/lookup.go +++ b/libgo/go/net/lookup.go @@ -4,7 +4,10 @@ package net -import "time" +import ( + "internal/singleflight" + "time" +) // protocols contains minimal mappings between internet protocol // names and numbers for platforms that don't have a complete list of @@ -22,36 +25,60 @@ var protocols = map[string]int{ // 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) { + // Make sure that no matter what we do later, host=="" is rejected. + // ParseIP, for example, does accept empty strings. + if host == "" { + return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host} + } + if ip := ParseIP(host); ip != nil { + return []string{host}, nil + } return lookupHost(host) } // 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) { - return lookupIPMerge(host) +func LookupIP(host string) (ips []IP, err error) { + // Make sure that no matter what we do later, host=="" is rejected. + // ParseIP, for example, does accept empty strings. + if host == "" { + return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host} + } + if ip := ParseIP(host); ip != nil { + return []IP{ip}, nil + } + addrs, err := lookupIPMerge(host) + if err != nil { + return + } + ips = make([]IP, len(addrs)) + for i, addr := range addrs { + ips[i] = addr.IP + } + return } -var lookupGroup singleflight +var lookupGroup singleflight.Group // lookupIPMerge wraps lookupIP, but makes sure that for any given // host, only one lookup is in-flight at a time. The returned memory // is always owned by the caller. -func lookupIPMerge(host string) (addrs []IP, err error) { +func lookupIPMerge(host string) (addrs []IPAddr, err error) { addrsi, err, shared := lookupGroup.Do(host, func() (interface{}, error) { - return lookupIP(host) + return testHookLookupIP(lookupIP, host) }) return lookupIPReturn(addrsi, err, shared) } // lookupIPReturn turns the return values from singleflight.Do into // the return values from LookupIP. -func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IP, error) { +func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IPAddr, error) { if err != nil { return nil, err } - addrs := addrsi.([]IP) + addrs := addrsi.([]IPAddr) if shared { - clone := make([]IP, len(addrs)) + clone := make([]IPAddr, len(addrs)) copy(clone, addrs) addrs = clone } @@ -59,7 +86,7 @@ func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IP, error) { } // lookupIPDeadline looks up a hostname with a deadline. -func lookupIPDeadline(host string, deadline time.Time) (addrs []IP, err error) { +func lookupIPDeadline(host string, deadline time.Time) (addrs []IPAddr, err error) { if deadline.IsZero() { return lookupIPMerge(host) } @@ -76,7 +103,7 @@ func lookupIPDeadline(host string, deadline time.Time) (addrs []IP, err error) { defer t.Stop() ch := lookupGroup.DoChan(host, func() (interface{}, error) { - return lookupIP(host) + return testHookLookupIP(lookupIP, host) }) select { @@ -90,7 +117,7 @@ func lookupIPDeadline(host string, deadline time.Time) (addrs []IP, err error) { return nil, errTimeout case r := <-ch: - return lookupIPReturn(r.v, r.err, r.shared) + return lookupIPReturn(r.Val, r.Err, r.Shared) } } @@ -121,22 +148,22 @@ func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err } // LookupMX returns the DNS MX records for the given domain name sorted by preference. -func LookupMX(name string) (mx []*MX, err error) { +func LookupMX(name string) (mxs []*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) { +func LookupNS(name string) (nss []*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) { +func LookupTXT(name string) (txts []string, err error) { return lookupTXT(name) } // LookupAddr performs a reverse lookup for the given address, returning a list // of names mapping to that address. -func LookupAddr(addr string) (name []string, err error) { +func LookupAddr(addr string) (names []string, err error) { return lookupAddr(addr) } diff --git a/libgo/go/net/lookup_plan9.go b/libgo/go/net/lookup_plan9.go index b80ac10e0d9..c6274640bb7 100644 --- a/libgo/go/net/lookup_plan9.go +++ b/libgo/go/net/lookup_plan9.go @@ -101,19 +101,18 @@ func lookupProtocol(name string) (proto int, err error) { if err != nil { return 0, err } - unknownProtoError := errors.New("unknown IP protocol specified: " + name) if len(lines) == 0 { - return 0, unknownProtoError + return 0, UnknownNetworkError(name) } f := getFields(lines[0]) if len(f) < 2 { - return 0, unknownProtoError + return 0, UnknownNetworkError(name) } s := f[1] if n, _, ok := dtoi(s, byteIndex(s, '=')+1); ok { return n, nil } - return 0, unknownProtoError + return 0, UnknownNetworkError(name) } func lookupHost(host string) (addrs []string, err error) { @@ -147,14 +146,16 @@ loop: return } -func lookupIP(host string) (ips []IP, err error) { - addrs, err := LookupHost(host) +func lookupIP(host string) (addrs []IPAddr, err error) { + lits, err := LookupHost(host) if err != nil { return } - for _, addr := range addrs { - if ip := ParseIP(addr); ip != nil { - ips = append(ips, ip) + for _, lit := range lits { + host, zone := splitHostZone(lit) + if ip := ParseIP(host); ip != nil { + addr := IPAddr{IP: ip, Zone: zone} + addrs = append(addrs, addr) } } return @@ -171,7 +172,7 @@ func lookupPort(network, service string) (port int, err error) { if err != nil { return } - unknownPortError := &AddrError{"unknown port", network + "/" + service} + unknownPortError := &AddrError{Err: "unknown port", Addr: network + "/" + service} if len(lines) == 0 { return 0, unknownPortError } diff --git a/libgo/go/net/lookup_stub.go b/libgo/go/net/lookup_stub.go index 502aafb2702..5636198f881 100644 --- a/libgo/go/net/lookup_stub.go +++ b/libgo/go/net/lookup_stub.go @@ -16,7 +16,7 @@ func lookupHost(host string) (addrs []string, err error) { return nil, syscall.ENOPROTOOPT } -func lookupIP(host string) (ips []IP, err error) { +func lookupIP(host string) (addrs []IPAddr, err error) { return nil, syscall.ENOPROTOOPT } diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go index 057e1322b99..86957b55756 100644 --- a/libgo/go/net/lookup_test.go +++ b/libgo/go/net/lookup_test.go @@ -2,18 +2,34 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// TODO It would be nice to use a mock DNS server, to eliminate -// external dependencies. - package net import ( - "flag" + "bytes" + "fmt" "strings" "testing" + "time" ) -var testExternal = flag.Bool("external", true, "allow use of external networks during long test") +func lookupLocalhost(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { + switch host { + case "localhost": + return []IPAddr{ + {IP: IPv4(127, 0, 0, 1)}, + {IP: IPv6loopback}, + }, nil + default: + return fn(host) + } +} + +// The Lookup APIs use various sources such as local database, DNS or +// mDNS, and may use platform-dependent DNS stub resolver if possible. +// The APIs accept any of forms for a query; host name in various +// encodings, UTF-8 encoded net name, domain name, FQDN or absolute +// FQDN, but the result would be one of the forms and it depends on +// the circumstances. var lookupGoogleSRVTests = []struct { service, proto, name string @@ -21,17 +37,30 @@ var lookupGoogleSRVTests = []struct { }{ { "xmpp-server", "tcp", "google.com", - ".google.com", ".google.com", + "google.com", "google.com", + }, + { + "xmpp-server", "tcp", "google.com.", + "google.com", "google.com", + }, + + // non-standard back door + { + "", "", "_xmpp-server._tcp.google.com", + "google.com", "google.com", }, { - "", "", "_xmpp-server._tcp.google.com", // non-standard back door - ".google.com", ".google.com", + "", "", "_xmpp-server._tcp.google.com.", + "google.com", "google.com", }, } func TestLookupGoogleSRV(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") + } + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } for _, tt := range lookupGoogleSRVTests { @@ -42,90 +71,128 @@ func TestLookupGoogleSRV(t *testing.T) { if len(srvs) == 0 { t.Error("got no record") } - if !strings.Contains(cname, tt.cname) { - t.Errorf("got %q; want %q", cname, tt.cname) + if !strings.HasSuffix(cname, tt.cname) && !strings.HasSuffix(cname, tt.cname+".") { + t.Errorf("got %s; want %s", cname, tt.cname) } for _, srv := range srvs { - if !strings.Contains(srv.Target, tt.target) { - t.Errorf("got %v; want a record containing %q", srv, tt.target) + if !strings.HasSuffix(srv.Target, tt.target) && !strings.HasSuffix(srv.Target, tt.target+".") { + t.Errorf("got %v; want a record containing %s", srv, tt.target) } } } } +var lookupGmailMXTests = []struct { + name, host string +}{ + {"gmail.com", "google.com"}, + {"gmail.com.", "google.com"}, +} + func TestLookupGmailMX(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - - mxs, err := LookupMX("gmail.com") - if err != nil { - t.Fatal(err) - } - if len(mxs) == 0 { - t.Error("got no record") + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } - for _, mx := range mxs { - if !strings.Contains(mx.Host, ".google.com") { - t.Errorf("got %v; want a record containing .google.com.", mx) + + for _, tt := range lookupGmailMXTests { + mxs, err := LookupMX(tt.name) + if err != nil { + t.Fatal(err) + } + if len(mxs) == 0 { + t.Error("got no record") + } + for _, mx := range mxs { + if !strings.HasSuffix(mx.Host, tt.host) && !strings.HasSuffix(mx.Host, tt.host+".") { + t.Errorf("got %v; want a record containing %s", mx, tt.host) + } } } } +var lookupGmailNSTests = []struct { + name, host string +}{ + {"gmail.com", "google.com"}, + {"gmail.com.", "google.com"}, +} + func TestLookupGmailNS(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - - nss, err := LookupNS("gmail.com") - if err != nil { - t.Fatal(err) - } - if len(nss) == 0 { - t.Error("got no record") + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } - for _, ns := range nss { - if !strings.Contains(ns.Host, ".google.com") { - t.Errorf("got %v; want a record containing .google.com.", ns) + + for _, tt := range lookupGmailNSTests { + nss, err := LookupNS(tt.name) + if err != nil { + t.Fatal(err) + } + if len(nss) == 0 { + t.Error("got no record") + } + for _, ns := range nss { + if !strings.HasSuffix(ns.Host, tt.host) && !strings.HasSuffix(ns.Host, tt.host+".") { + t.Errorf("got %v; want a record containing %s", ns, tt.host) + } } } } +var lookupGmailTXTTests = []struct { + name, txt, host string +}{ + {"gmail.com", "spf", "google.com"}, + {"gmail.com.", "spf", "google.com"}, +} + func TestLookupGmailTXT(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - - txts, err := LookupTXT("gmail.com") - if err != nil { - t.Fatal(err) + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } - if len(txts) == 0 { - t.Error("got no record") - } - for _, txt := range txts { - if !strings.Contains(txt, "spf") { - t.Errorf("got %q; want a spf record", txt) + + for _, tt := range lookupGmailTXTTests { + txts, err := LookupTXT(tt.name) + if err != nil { + t.Fatal(err) + } + if len(txts) == 0 { + t.Error("got no record") + } + for _, txt := range txts { + if !strings.Contains(txt, tt.txt) || (!strings.HasSuffix(txt, tt.host) && !strings.HasSuffix(txt, tt.host+".")) { + t.Errorf("got %s; want a record containing %s, %s", txt, tt.txt, tt.host) + } } } } -var lookupGooglePublicDNSAddrs = []struct { - addr string - name string +var lookupGooglePublicDNSAddrTests = []struct { + addr, name string }{ - {"8.8.8.8", ".google.com."}, - {"8.8.4.4", ".google.com."}, - {"2001:4860:4860::8888", ".google.com."}, - {"2001:4860:4860::8844", ".google.com."}, + {"8.8.8.8", ".google.com"}, + {"8.8.4.4", ".google.com"}, + {"2001:4860:4860::8888", ".google.com"}, + {"2001:4860:4860::8844", ".google.com"}, } func TestLookupGooglePublicDNSAddr(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") + } + if !supportsIPv4 || !supportsIPv6 || !*testIPv4 || !*testIPv6 { + t.Skip("both IPv4 and IPv6 are required") } - for _, tt := range lookupGooglePublicDNSAddrs { + for _, tt := range lookupGooglePublicDNSAddrTests { names, err := LookupAddr(tt.addr) if err != nil { t.Fatal(err) @@ -134,61 +201,97 @@ func TestLookupGooglePublicDNSAddr(t *testing.T) { t.Error("got no record") } for _, name := range names { - if !strings.HasSuffix(name, tt.name) { - t.Errorf("got %q; want a record containing %q", name, tt.name) + if !strings.HasSuffix(name, tt.name) && !strings.HasSuffix(name, tt.name+".") { + t.Errorf("got %s; want a record containing %s", name, tt.name) } } } } +var lookupIANACNAMETests = []struct { + name, cname string +}{ + {"www.iana.org", "icann.org"}, + {"www.iana.org.", "icann.org"}, +} + func TestLookupIANACNAME(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - - cname, err := LookupCNAME("www.iana.org") - if err != nil { - t.Fatal(err) + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } - if !strings.HasSuffix(cname, ".icann.org.") { - t.Errorf("got %q; want a record containing .icann.org.", cname) + + for _, tt := range lookupIANACNAMETests { + cname, err := LookupCNAME(tt.name) + if err != nil { + t.Fatal(err) + } + if !strings.HasSuffix(cname, tt.cname) && !strings.HasSuffix(cname, tt.cname+".") { + t.Errorf("got %s; want a record containing %s", cname, tt.cname) + } } } +var lookupGoogleHostTests = []struct { + name string +}{ + {"google.com"}, + {"google.com."}, +} + func TestLookupGoogleHost(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - - addrs, err := LookupHost("google.com") - if err != nil { - t.Fatal(err) - } - if len(addrs) == 0 { - t.Error("got no record") + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } - for _, addr := range addrs { - if ParseIP(addr) == nil { - t.Errorf("got %q; want a literal ip address", addr) + + for _, tt := range lookupGoogleHostTests { + addrs, err := LookupHost(tt.name) + if err != nil { + t.Fatal(err) + } + if len(addrs) == 0 { + t.Error("got no record") + } + for _, addr := range addrs { + if ParseIP(addr) == nil { + t.Errorf("got %q; want a literal IP address", addr) + } } } } +var lookupGoogleIPTests = []struct { + name string +}{ + {"google.com"}, + {"google.com."}, +} + func TestLookupGoogleIP(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } - - ips, err := LookupIP("google.com") - if err != nil { - t.Fatal(err) - } - if len(ips) == 0 { - t.Error("got no record") + if !supportsIPv4 || !*testIPv4 { + t.Skip("IPv4 is required") } - for _, ip := range ips { - if ip.To4() == nil && ip.To16() == nil { - t.Errorf("got %v; want an ip address", ip) + + for _, tt := range lookupGoogleIPTests { + ips, err := LookupIP(tt.name) + if err != nil { + t.Fatal(err) + } + if len(ips) == 0 { + t.Error("got no record") + } + for _, ip := range ips { + if ip.To4() == nil && ip.To16() == nil { + t.Errorf("got %v; want an IP address", ip) + } } } } @@ -229,3 +332,172 @@ func TestReverseAddress(t *testing.T) { } } } + +func TestLookupIPDeadline(t *testing.T) { + if !*testDNSFlood { + t.Skip("test disabled; use -dnsflood to enable") + } + + const N = 5000 + const timeout = 3 * time.Second + c := make(chan error, 2*N) + for i := 0; i < N; i++ { + name := fmt.Sprintf("%d.net-test.golang.org", i) + go func() { + _, err := lookupIPDeadline(name, time.Now().Add(timeout/2)) + c <- err + }() + go func() { + _, err := lookupIPDeadline(name, time.Now().Add(timeout)) + c <- err + }() + } + qstats := struct { + succeeded, failed int + timeout, temporary, other int + unknown int + }{} + deadline := time.After(timeout + time.Second) + for i := 0; i < 2*N; i++ { + select { + case <-deadline: + t.Fatal("deadline exceeded") + case err := <-c: + switch err := err.(type) { + case nil: + qstats.succeeded++ + case Error: + qstats.failed++ + if err.Timeout() { + qstats.timeout++ + } + if err.Temporary() { + qstats.temporary++ + } + if !err.Timeout() && !err.Temporary() { + qstats.other++ + } + default: + qstats.failed++ + qstats.unknown++ + } + } + } + + // A high volume of DNS queries for sub-domain of golang.org + // would be coordinated by authoritative or recursive server, + // or stub resolver which implements query-response rate + // limitation, so we can expect some query successes and more + // failures including timeout, temporary and other here. + // As a rule, unknown must not be shown but it might possibly + // happen due to issue 4856 for now. + t.Logf("%v succeeded, %v failed (%v timeout, %v temporary, %v other, %v unknown)", qstats.succeeded, qstats.failed, qstats.timeout, qstats.temporary, qstats.other, qstats.unknown) +} + +func TestLookupDots(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skipf("skipping external network test") + } + + fixup := forceGoDNS() + defer fixup() + testDots(t, "go") + + if forceCgoDNS() { + testDots(t, "cgo") + } +} + +func testDots(t *testing.T, mode string) { + names, err := LookupAddr("8.8.8.8") // Google dns server + if err != nil { + t.Errorf("LookupAddr(8.8.8.8): %v (mode=%v)", err, mode) + } else { + for _, name := range names { + if !strings.HasSuffix(name, ".google.com.") { + t.Errorf("LookupAddr(8.8.8.8) = %v, want names ending in .google.com. with trailing dot (mode=%v)", names, mode) + break + } + } + } + + cname, err := LookupCNAME("www.mit.edu") + if err != nil || !strings.HasSuffix(cname, ".") { + t.Errorf("LookupCNAME(www.mit.edu) = %v, %v, want cname ending in . with trailing dot (mode=%v)", cname, err, mode) + } + + mxs, err := LookupMX("google.com") + if err != nil { + t.Errorf("LookupMX(google.com): %v (mode=%v)", err, mode) + } else { + for _, mx := range mxs { + if !strings.HasSuffix(mx.Host, ".google.com.") { + t.Errorf("LookupMX(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", mxString(mxs), mode) + break + } + } + } + + nss, err := LookupNS("google.com") + if err != nil { + t.Errorf("LookupNS(google.com): %v (mode=%v)", err, mode) + } else { + for _, ns := range nss { + if !strings.HasSuffix(ns.Host, ".google.com.") { + t.Errorf("LookupNS(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", nsString(nss), mode) + break + } + } + } + + cname, srvs, err := LookupSRV("xmpp-server", "tcp", "google.com") + if err != nil { + t.Errorf("LookupSRV(xmpp-server, tcp, google.com): %v (mode=%v)", err, mode) + } else { + if !strings.HasSuffix(cname, ".google.com.") { + t.Errorf("LookupSRV(xmpp-server, tcp, google.com) returned cname=%v, want name ending in .google.com. with trailing dot (mode=%v)", cname, mode) + } + for _, srv := range srvs { + if !strings.HasSuffix(srv.Target, ".google.com.") { + t.Errorf("LookupSRV(xmpp-server, tcp, google.com) returned addrs=%v, want names ending in .google.com. with trailing dot (mode=%v)", srvString(srvs), mode) + break + } + } + } +} + +func mxString(mxs []*MX) string { + var buf bytes.Buffer + sep := "" + fmt.Fprintf(&buf, "[") + for _, mx := range mxs { + fmt.Fprintf(&buf, "%s%s:%d", sep, mx.Host, mx.Pref) + sep = " " + } + fmt.Fprintf(&buf, "]") + return buf.String() +} + +func nsString(nss []*NS) string { + var buf bytes.Buffer + sep := "" + fmt.Fprintf(&buf, "[") + for _, ns := range nss { + fmt.Fprintf(&buf, "%s%s", sep, ns.Host) + sep = " " + } + fmt.Fprintf(&buf, "]") + return buf.String() +} + +func srvString(srvs []*SRV) string { + var buf bytes.Buffer + sep := "" + fmt.Fprintf(&buf, "[") + for _, srv := range srvs { + fmt.Fprintf(&buf, "%s%s:%d:%d:%d", sep, srv.Target, srv.Port, srv.Priority, srv.Weight) + sep = " " + } + fmt.Fprintf(&buf, "]") + return buf.String() +} diff --git a/libgo/go/net/lookup_unix.go b/libgo/go/net/lookup_unix.go index a54578456d7..a64da8bcb50 100644 --- a/libgo/go/net/lookup_unix.go +++ b/libgo/go/net/lookup_unix.go @@ -6,10 +6,7 @@ package net -import ( - "errors" - "sync" -) +import "sync" var onceReadProtocols sync.Once @@ -43,126 +40,120 @@ func readProtocols() { // lookupProtocol looks up IP protocol name in /etc/protocols and // returns correspondent protocol number. -func lookupProtocol(name string) (proto int, err error) { +func lookupProtocol(name string) (int, error) { onceReadProtocols.Do(readProtocols) proto, found := protocols[name] if !found { - return 0, errors.New("unknown IP protocol specified: " + name) + return 0, &AddrError{Err: "unknown IP protocol specified", Addr: name} } - return + return proto, nil } func lookupHost(host string) (addrs []string, err error) { - addrs, err, ok := cgoLookupHost(host) - if !ok { - addrs, err = goLookupHost(host) + order := systemConf().hostLookupOrder(host) + if order == hostLookupCgo { + if addrs, err, ok := cgoLookupHost(host); ok { + return addrs, err + } + // cgo not available (or netgo); fall back to Go's DNS resolver + order = hostLookupFilesDNS } - return + return goLookupHostOrder(host, order) } -func lookupIP(host string) (addrs []IP, err error) { - addrs, err, ok := cgoLookupIP(host) - if !ok { - addrs, err = goLookupIP(host) +func lookupIP(host string) (addrs []IPAddr, err error) { + order := systemConf().hostLookupOrder(host) + if order == hostLookupCgo { + if addrs, err, ok := cgoLookupIP(host); ok { + return addrs, err + } + // cgo not available (or netgo); fall back to Go's DNS resolver + order = hostLookupFilesDNS } - return + return goLookupIPOrder(host, order) } -func lookupPort(network, service string) (port int, err error) { - port, err, ok := cgoLookupPort(network, service) - if !ok { - port, err = goLookupPort(network, service) +func lookupPort(network, service string) (int, error) { + if systemConf().canUseCgo() { + if port, err, ok := cgoLookupPort(network, service); ok { + return port, err + } } - return + return goLookupPort(network, service) } -func lookupCNAME(name string) (cname string, err error) { - cname, err, ok := cgoLookupCNAME(name) - if !ok { - cname, err = goLookupCNAME(name) +func lookupCNAME(name string) (string, error) { + if systemConf().canUseCgo() { + if cname, err, ok := cgoLookupCNAME(name); ok { + return cname, err + } } - return + return goLookupCNAME(name) } -func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { +func lookupSRV(service, proto, name string) (string, []*SRV, error) { var target string if service == "" && proto == "" { target = name } else { target = "_" + service + "._" + proto + "." + name } - var records []dnsRR - cname, records, err = lookup(target, dnsTypeSRV) + cname, rrs, err := lookup(target, dnsTypeSRV) if err != nil { - return + return "", nil, err } - addrs = make([]*SRV, len(records)) - for i, rr := range records { - r := rr.(*dnsRR_SRV) - addrs[i] = &SRV{r.Target, r.Port, r.Priority, r.Weight} + srvs := make([]*SRV, len(rrs)) + for i, rr := range rrs { + rr := rr.(*dnsRR_SRV) + srvs[i] = &SRV{Target: rr.Target, Port: rr.Port, Priority: rr.Priority, Weight: rr.Weight} } - byPriorityWeight(addrs).sort() - return + byPriorityWeight(srvs).sort() + return cname, srvs, nil } -func lookupMX(name string) (mx []*MX, err error) { - _, records, err := lookup(name, dnsTypeMX) +func lookupMX(name string) ([]*MX, error) { + _, rrs, err := lookup(name, dnsTypeMX) if err != nil { - return + return nil, err } - mx = make([]*MX, len(records)) - for i, rr := range records { - r := rr.(*dnsRR_MX) - mx[i] = &MX{r.Mx, r.Pref} + mxs := make([]*MX, len(rrs)) + for i, rr := range rrs { + rr := rr.(*dnsRR_MX) + mxs[i] = &MX{Host: rr.Mx, Pref: rr.Pref} } - byPref(mx).sort() - return + byPref(mxs).sort() + return mxs, nil } -func lookupNS(name string) (ns []*NS, err error) { - _, records, err := lookup(name, dnsTypeNS) +func lookupNS(name string) ([]*NS, error) { + _, rrs, err := lookup(name, dnsTypeNS) if err != nil { - return + return nil, err } - ns = make([]*NS, len(records)) - for i, r := range records { - r := r.(*dnsRR_NS) - ns[i] = &NS{r.Ns} + nss := make([]*NS, len(rrs)) + for i, rr := range rrs { + nss[i] = &NS{Host: rr.(*dnsRR_NS).Ns} } - return + return nss, nil } -func lookupTXT(name string) (txt []string, err error) { - _, records, err := lookup(name, dnsTypeTXT) +func lookupTXT(name string) ([]string, error) { + _, rrs, err := lookup(name, dnsTypeTXT) if err != nil { - return + return nil, err } - txt = make([]string, len(records)) - for i, r := range records { - txt[i] = r.(*dnsRR_TXT).Txt + txts := make([]string, len(rrs)) + for i, rr := range rrs { + txts[i] = rr.(*dnsRR_TXT).Txt } - return + return txts, nil } -func lookupAddr(addr string) (name []string, err error) { - name = lookupStaticAddr(addr) - if len(name) > 0 { - return - } - var arpa string - arpa, err = reverseaddr(addr) - if err != nil { - return - } - var records []dnsRR - _, records, err = lookup(arpa, dnsTypePTR) - if err != nil { - return - } - name = make([]string, len(records)) - for i := range records { - r := records[i].(*dnsRR_PTR) - name[i] = r.Ptr +func lookupAddr(addr string) ([]string, error) { + if systemConf().canUseCgo() { + if ptrs, err, ok := cgoLookupPTR(addr); ok { + return ptrs, err + } } - return + return goLookupPTR(addr) } diff --git a/libgo/go/net/lookup_windows.go b/libgo/go/net/lookup_windows.go index 6a925b0a7ad..1b6d392f660 100644 --- a/libgo/go/net/lookup_windows.go +++ b/libgo/go/net/lookup_windows.go @@ -19,13 +19,13 @@ var ( func getprotobyname(name string) (proto int, err error) { p, err := syscall.GetProtoByName(name) if err != nil { - return 0, os.NewSyscallError("GetProtoByName", err) + return 0, os.NewSyscallError("getorotobyname", err) } return int(p.Proto), nil } // lookupProtocol looks up IP protocol name and returns correspondent protocol number. -func lookupProtocol(name string) (proto int, err error) { +func lookupProtocol(name string) (int, error) { // GetProtoByName return value is stored in thread local storage. // Start new os thread before the call to prevent races. type result struct { @@ -46,47 +46,48 @@ func lookupProtocol(name string) (proto int, err error) { if proto, ok := protocols[name]; ok { return proto, nil } + r.err = &DNSError{Err: r.err.Error(), Name: name} } return r.proto, r.err } -func lookupHost(name string) (addrs []string, err error) { +func lookupHost(name string) ([]string, error) { ips, err := LookupIP(name) if err != nil { - return + return nil, err } - addrs = make([]string, 0, len(ips)) + addrs := make([]string, 0, len(ips)) for _, ip := range ips { addrs = append(addrs, ip.String()) } - return + return addrs, nil } -func gethostbyname(name string) (addrs []IP, err error) { +func gethostbyname(name string) (addrs []IPAddr, err error) { // caller already acquired thread h, err := syscall.GetHostByName(name) if err != nil { - return nil, os.NewSyscallError("GetHostByName", err) + return nil, os.NewSyscallError("gethostbyname", err) } switch h.AddrType { case syscall.AF_INET: i := 0 - addrs = make([]IP, 100) // plenty of room to grow + addrs = make([]IPAddr, 100) // plenty of room to grow for p := (*[100](*[4]byte))(unsafe.Pointer(h.AddrList)); i < cap(addrs) && p[i] != nil; i++ { - addrs[i] = IPv4(p[i][0], p[i][1], p[i][2], p[i][3]) + addrs[i] = IPAddr{IP: IPv4(p[i][0], p[i][1], p[i][2], p[i][3])} } addrs = addrs[0:i] default: // TODO(vcc): Implement non IPv4 address lookups. - return nil, os.NewSyscallError("LookupIP", syscall.EWINDOWS) + return nil, syscall.EWINDOWS } return addrs, nil } -func oldLookupIP(name string) (addrs []IP, err error) { +func oldLookupIP(name string) ([]IPAddr, error) { // GetHostByName return value is stored in thread local storage. // Start new os thread before the call to prevent races. type result struct { - addrs []IP + addrs []IPAddr err error } ch := make(chan result) @@ -99,10 +100,13 @@ func oldLookupIP(name string) (addrs []IP, err error) { ch <- result{addrs: addrs, err: err} }() r := <-ch + if r.err != nil { + r.err = &DNSError{Err: r.err.Error(), Name: name} + } return r.addrs, r.err } -func newLookupIP(name string) (addrs []IP, err error) { +func newLookupIP(name string) ([]IPAddr, error) { acquireThread() defer releaseThread() hints := syscall.AddrinfoW{ @@ -113,27 +117,28 @@ func newLookupIP(name string) (addrs []IP, err error) { var result *syscall.AddrinfoW e := syscall.GetAddrInfoW(syscall.StringToUTF16Ptr(name), nil, &hints, &result) if e != nil { - return nil, os.NewSyscallError("GetAddrInfoW", e) + return nil, &DNSError{Err: os.NewSyscallError("getaddrinfow", e).Error(), Name: name} } defer syscall.FreeAddrInfoW(result) - addrs = make([]IP, 0, 5) + addrs := make([]IPAddr, 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])) + addrs = append(addrs, IPAddr{IP: 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]}) + zone := zoneToString(int((*syscall.RawSockaddrInet6)(addr).Scope_id)) + addrs = append(addrs, IPAddr{IP: 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]}, Zone: zone}) default: - return nil, os.NewSyscallError("LookupIP", syscall.EWINDOWS) + return nil, &DNSError{Err: syscall.EWINDOWS.Error(), Name: name} } } return addrs, nil } -func getservbyname(network, service string) (port int, err error) { +func getservbyname(network, service string) (int, error) { acquireThread() defer releaseThread() switch network { @@ -144,12 +149,12 @@ func getservbyname(network, service string) (port int, err error) { } s, err := syscall.GetServByName(service, network) if err != nil { - return 0, os.NewSyscallError("GetServByName", err) + return 0, os.NewSyscallError("getservbyname", err) } return int(syscall.Ntohs(s.Port)), nil } -func oldLookupPort(network, service string) (port int, err error) { +func oldLookupPort(network, service string) (int, error) { // GetServByName return value is stored in thread local storage. // Start new os thread before the call to prevent races. type result struct { @@ -166,10 +171,13 @@ func oldLookupPort(network, service string) (port int, err error) { ch <- result{port: port, err: err} }() r := <-ch + if r.err != nil { + r.err = &DNSError{Err: r.err.Error(), Name: network + "/" + service} + } return r.port, r.err } -func newLookupPort(network, service string) (port int, err error) { +func newLookupPort(network, service string) (int, error) { acquireThread() defer releaseThread() var stype int32 @@ -187,11 +195,11 @@ func newLookupPort(network, service string) (port int, err error) { var result *syscall.AddrinfoW e := syscall.GetAddrInfoW(nil, syscall.StringToUTF16Ptr(service), &hints, &result) if e != nil { - return 0, os.NewSyscallError("GetAddrInfoW", e) + return 0, &DNSError{Err: os.NewSyscallError("getaddrinfow", e).Error(), Name: network + "/" + service} } defer syscall.FreeAddrInfoW(result) if result == nil { - return 0, os.NewSyscallError("LookupPort", syscall.EINVAL) + return 0, &DNSError{Err: syscall.EINVAL.Error(), Name: network + "/" + service} } addr := unsafe.Pointer(result.Addr) switch result.Family { @@ -202,10 +210,10 @@ func newLookupPort(network, service string) (port int, err error) { a := (*syscall.RawSockaddrInet6)(addr) return int(syscall.Ntohs(a.Port)), nil } - return 0, os.NewSyscallError("LookupPort", syscall.EINVAL) + return 0, &DNSError{Err: syscall.EINVAL.Error(), Name: network + "/" + service} } -func lookupCNAME(name string) (cname string, err error) { +func lookupCNAME(name string) (string, error) { acquireThread() defer releaseThread() var r *syscall.DNSRecord @@ -219,16 +227,16 @@ func lookupCNAME(name string) (cname string, err error) { return name, nil } if e != nil { - return "", os.NewSyscallError("LookupCNAME", e) + return "", &DNSError{Err: os.NewSyscallError("dnsquery", e).Error(), Name: name} } defer syscall.DnsRecordListFree(r, 1) resolved := resolveCNAME(syscall.StringToUTF16Ptr(name), r) - cname = syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(resolved))[:]) + "." - return + cname := syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(resolved))[:]) + "." + return cname, nil } -func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { +func lookupSRV(service, proto, name string) (string, []*SRV, error) { acquireThread() defer releaseThread() var target string @@ -240,78 +248,78 @@ func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err var r *syscall.DNSRecord e := syscall.DnsQuery(target, syscall.DNS_TYPE_SRV, 0, nil, &r, nil) if e != nil { - return "", nil, os.NewSyscallError("LookupSRV", e) + return "", nil, &DNSError{Err: os.NewSyscallError("dnsquery", e).Error(), Name: target} } defer syscall.DnsRecordListFree(r, 1) - addrs = make([]*SRV, 0, 10) + srvs := make([]*SRV, 0, 10) for _, p := range validRecs(r, syscall.DNS_TYPE_SRV, target) { v := (*syscall.DNSSRVData)(unsafe.Pointer(&p.Data[0])) - addrs = append(addrs, &SRV{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Target))[:]), v.Port, v.Priority, v.Weight}) + srvs = append(srvs, &SRV{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Target))[:]), v.Port, v.Priority, v.Weight}) } - byPriorityWeight(addrs).sort() - return name, addrs, nil + byPriorityWeight(srvs).sort() + return name, srvs, nil } -func lookupMX(name string) (mx []*MX, err error) { +func lookupMX(name string) ([]*MX, error) { acquireThread() defer releaseThread() var r *syscall.DNSRecord e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &r, nil) if e != nil { - return nil, os.NewSyscallError("LookupMX", e) + return nil, &DNSError{Err: os.NewSyscallError("dnsquery", e).Error(), Name: name} } defer syscall.DnsRecordListFree(r, 1) - mx = make([]*MX, 0, 10) + mxs := make([]*MX, 0, 10) for _, p := range validRecs(r, syscall.DNS_TYPE_MX, name) { v := (*syscall.DNSMXData)(unsafe.Pointer(&p.Data[0])) - mx = append(mx, &MX{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.NameExchange))[:]) + ".", v.Preference}) + mxs = append(mxs, &MX{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.NameExchange))[:]) + ".", v.Preference}) } - byPref(mx).sort() - return mx, nil + byPref(mxs).sort() + return mxs, nil } -func lookupNS(name string) (ns []*NS, err error) { +func lookupNS(name string) ([]*NS, error) { acquireThread() defer releaseThread() 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) + return nil, &DNSError{Err: os.NewSyscallError("dnsquery", e).Error(), Name: name} } defer syscall.DnsRecordListFree(r, 1) - ns = make([]*NS, 0, 10) + nss := make([]*NS, 0, 10) for _, p := range validRecs(r, syscall.DNS_TYPE_NS, name) { v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) - ns = append(ns, &NS{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]) + "."}) + nss = append(nss, &NS{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]) + "."}) } - return ns, nil + return nss, nil } -func lookupTXT(name string) (txt []string, err error) { +func lookupTXT(name string) ([]string, error) { acquireThread() defer releaseThread() var r *syscall.DNSRecord e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil) if e != nil { - return nil, os.NewSyscallError("LookupTXT", e) + return nil, &DNSError{Err: os.NewSyscallError("dnsquery", e).Error(), Name: name} } defer syscall.DnsRecordListFree(r, 1) - txt = make([]string, 0, 10) + txts := make([]string, 0, 10) for _, p := range validRecs(r, syscall.DNS_TYPE_TEXT, name) { d := (*syscall.DNSTXTData)(unsafe.Pointer(&p.Data[0])) for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount] { s := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(v))[:]) - txt = append(txt, s) + txts = append(txts, s) } } - return + return txts, nil } -func lookupAddr(addr string) (name []string, err error) { +func lookupAddr(addr string) ([]string, error) { acquireThread() defer releaseThread() arpa, err := reverseaddr(addr) @@ -321,16 +329,16 @@ func lookupAddr(addr string) (name []string, err error) { var r *syscall.DNSRecord e := syscall.DnsQuery(arpa, syscall.DNS_TYPE_PTR, 0, nil, &r, nil) if e != nil { - return nil, os.NewSyscallError("LookupAddr", e) + return nil, &DNSError{Err: os.NewSyscallError("dnsquery", e).Error(), Name: addr} } defer syscall.DnsRecordListFree(r, 1) - name = make([]string, 0, 10) + ptrs := make([]string, 0, 10) for _, p := range validRecs(r, syscall.DNS_TYPE_PTR, arpa) { v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) - name = append(name, syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:])) + ptrs = append(ptrs, syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:])) } - return name, nil + return ptrs, nil } const dnsSectionMask = 0x0003 diff --git a/libgo/go/net/mac.go b/libgo/go/net/mac.go index d616b1f689f..8594a9146ae 100644 --- a/libgo/go/net/mac.go +++ b/libgo/go/net/mac.go @@ -2,12 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// MAC address manipulations - package net -import "errors" - const hexDigit = "0123456789abcdef" // A HardwareAddr represents a physical hardware address. @@ -82,5 +78,5 @@ func ParseMAC(s string) (hw HardwareAddr, err error) { return hw, nil error: - return nil, errors.New("invalid MAC address: " + s) + return nil, &AddrError{Err: "invalid MAC address", Addr: s} } diff --git a/libgo/go/net/mac_test.go b/libgo/go/net/mac_test.go index 8f9dc6685f8..0af0c014f50 100644 --- a/libgo/go/net/mac_test.go +++ b/libgo/go/net/mac_test.go @@ -10,7 +10,7 @@ import ( "testing" ) -var mactests = []struct { +var parseMACTests = []struct { in string out HardwareAddr err string @@ -36,19 +36,18 @@ var mactests = []struct { {"0123.4567.89AB.CDEF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""}, } -func match(err error, s string) bool { - if s == "" { - return err == nil +func TestParseMAC(t *testing.T) { + match := func(err error, s string) bool { + if s == "" { + return err == nil + } + return err != nil && strings.Contains(err.Error(), s) } - return err != nil && strings.Contains(err.Error(), s) -} -func TestMACParseString(t *testing.T) { - for i, tt := range mactests { + for i, tt := range parseMACTests { out, err := ParseMAC(tt.in) if !reflect.DeepEqual(out, tt.out) || !match(err, tt.err) { - t.Errorf("ParseMAC(%q) = %v, %v, want %v, %v", tt.in, out, err, tt.out, - tt.err) + t.Errorf("ParseMAC(%q) = %v, %v, want %v, %v", tt.in, out, err, tt.out, tt.err) } if tt.err == "" { // Verify that serialization works too, and that it round-trips. diff --git a/libgo/go/net/mail/example_test.go b/libgo/go/net/mail/example_test.go new file mode 100644 index 00000000000..972cfd6c424 --- /dev/null +++ b/libgo/go/net/mail/example_test.go @@ -0,0 +1,79 @@ +// Copyright 2015 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 ignore + +package mail_test + +import ( + "fmt" + "io/ioutil" + "log" + "net/mail" + "strings" +) + +func ExampleParseAddressList() { + const list = "Alice <alice@example.com>, Bob <bob@example.com>, Eve <eve@example.com>" + emails, err := mail.ParseAddressList(list) + if err != nil { + log.Fatal(err) + } + + for _, v := range emails { + fmt.Println(v.Name, v.Address) + } + + // Output: + // Alice alice@example.com + // Bob bob@example.com + // Eve eve@example.com +} + +func ExampleParseAddress() { + e, err := mail.ParseAddress("Alice <alice@example.com>") + if err != nil { + log.Fatal(err) + } + + fmt.Println(e.Name, e.Address) + + // Output: + // Alice alice@example.com +} + +func ExampleReadMessage() { + msg := `Date: Mon, 23 Jun 2015 11:40:36 -0400 +From: Gopher <from@example.com> +To: Another Gopher <to@example.com> +Subject: Gophers at Gophercon + +Message body +` + + r := strings.NewReader(msg) + m, err := mail.ReadMessage(r) + if err != nil { + log.Fatal(err) + } + + header := m.Header + fmt.Println("Date:", header.Get("Date")) + fmt.Println("From:", header.Get("From")) + fmt.Println("To:", header.Get("To")) + fmt.Println("Subject:", header.Get("Subject")) + + body, err := ioutil.ReadAll(m.Body) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s", body) + + // Output: + // Date: Mon, 23 Jun 2015 11:40:36 -0400 + // From: Gopher <from@example.com> + // To: Another Gopher <to@example.com> + // Subject: Gophers at Gophercon + // Message body +} diff --git a/libgo/go/net/mail/message.go b/libgo/go/net/mail/message.go index 19aa888d872..266ac50a38d 100644 --- a/libgo/go/net/mail/message.go +++ b/libgo/go/net/mail/message.go @@ -18,17 +18,14 @@ package mail import ( "bufio" "bytes" - "encoding/base64" "errors" "fmt" "io" - "io/ioutil" "log" + "mime" "net/textproto" - "strconv" "strings" "time" - "unicode" ) var debug = debugT(false) @@ -141,22 +138,79 @@ type Address struct { // Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>" func ParseAddress(address string) (*Address, error) { - return newAddrParser(address).parseAddress() + return (&addrParser{s: address}).parseAddress() } // ParseAddressList parses the given string as a list of addresses. func ParseAddressList(list string) ([]*Address, error) { - return newAddrParser(list).parseAddressList() + return (&addrParser{s: list}).parseAddressList() +} + +// An AddressParser is an RFC 5322 address parser. +type AddressParser struct { + // WordDecoder optionally specifies a decoder for RFC 2047 encoded-words. + WordDecoder *mime.WordDecoder +} + +// Parse parses a single RFC 5322 address of the +// form "Gogh Fir <gf@example.com>" or "foo@example.com". +func (p *AddressParser) Parse(address string) (*Address, error) { + return (&addrParser{s: address, dec: p.WordDecoder}).parseAddress() +} + +// ParseList parses the given string as a list of comma-separated addresses +// of the form "Gogh Fir <gf@example.com>" or "foo@example.com". +func (p *AddressParser) ParseList(list string) ([]*Address, error) { + return (&addrParser{s: list, dec: p.WordDecoder}).parseAddressList() } // String formats the address as a valid RFC 5322 address. // If the address's name contains non-ASCII characters // the name will be rendered according to RFC 2047. func (a *Address) String() string { - s := "<" + a.Address + ">" + + // Format address local@domain + at := strings.LastIndex(a.Address, "@") + var local, domain string + if at < 0 { + // This is a malformed address ("@" is required in addr-spec); + // treat the whole address as local-part. + local = a.Address + } else { + local, domain = a.Address[:at], a.Address[at+1:] + } + + // Add quotes if needed + // TODO: rendering quoted local part and rendering printable name + // should be merged in helper function. + quoteLocal := false + for i := 0; i < len(local); i++ { + ch := local[i] + if isAtext(ch, false) { + continue + } + if ch == '.' { + // Dots are okay if they are surrounded by atext. + // We only need to check that the previous byte is + // not a dot, and this isn't the end of the string. + if i > 0 && local[i-1] != '.' && i < len(local)-1 { + continue + } + } + quoteLocal = true + break + } + if quoteLocal { + local = quoteString(local) + + } + + s := "<" + local + "@" + domain + ">" + if a.Name == "" { return s } + // If every character is printable ASCII, quoting is simple. allPrintable := true for i := 0; i < len(a.Name); i++ { @@ -180,28 +234,12 @@ func (a *Address) String() string { return b.String() } - // UTF-8 "Q" encoding - b := bytes.NewBufferString("=?utf-8?q?") - for i := 0; i < len(a.Name); i++ { - switch c := a.Name[i]; { - case c == ' ': - b.WriteByte('_') - case isVchar(c) && c != '=' && c != '?' && c != '_': - b.WriteByte(c) - default: - fmt.Fprintf(b, "=%02X", c) - } - } - b.WriteString("?= ") - b.WriteString(s) - return b.String() + return mime.QEncoding.Encode("utf-8", a.Name) + " " + s } -type addrParser []byte - -func newAddrParser(s string) *addrParser { - p := addrParser(s) - return &p +type addrParser struct { + s string + dec *mime.WordDecoder // may be nil } func (p *addrParser) parseAddressList() ([]*Address, error) { @@ -227,7 +265,7 @@ func (p *addrParser) parseAddressList() ([]*Address, error) { // parseAddress parses a single RFC 5322 address at the start of p. func (p *addrParser) parseAddress() (addr *Address, err error) { - debug.Printf("parseAddress: %q", *p) + debug.Printf("parseAddress: %q", p.s) p.skipSpace() if p.empty() { return nil, errors.New("mail: no address") @@ -246,7 +284,7 @@ func (p *addrParser) parseAddress() (addr *Address, err error) { }, err } debug.Printf("parseAddress: not an addr-spec: %v", err) - debug.Printf("parseAddress: state is now %q", *p) + debug.Printf("parseAddress: state is now %q", p.s) // display-name var displayName string @@ -280,7 +318,7 @@ func (p *addrParser) parseAddress() (addr *Address, err error) { // consumeAddrSpec parses a single RFC 5322 addr-spec at the start of p. func (p *addrParser) consumeAddrSpec() (spec string, err error) { - debug.Printf("consumeAddrSpec: %q", *p) + debug.Printf("consumeAddrSpec: %q", p.s) orig := *p defer func() { @@ -302,7 +340,7 @@ func (p *addrParser) consumeAddrSpec() (spec string, err error) { } else { // dot-atom debug.Printf("consumeAddrSpec: parsing dot-atom") - localPart, err = p.consumeAtom(true) + localPart, err = p.consumeAtom(true, false) } if err != nil { debug.Printf("consumeAddrSpec: failed: %v", err) @@ -320,7 +358,7 @@ func (p *addrParser) consumeAddrSpec() (spec string, err error) { return "", errors.New("mail: no domain in addr-spec") } // TODO(dsymonds): Handle domain-literal - domain, err = p.consumeAtom(true) + domain, err = p.consumeAtom(true, false) if err != nil { return "", err } @@ -330,7 +368,7 @@ func (p *addrParser) consumeAddrSpec() (spec string, err error) { // consumePhrase parses the RFC 5322 phrase at the start of p. func (p *addrParser) consumePhrase() (phrase string, err error) { - debug.Printf("consumePhrase: [%s]", *p) + debug.Printf("consumePhrase: [%s]", p.s) // phrase = 1*word var words []string for { @@ -347,12 +385,11 @@ func (p *addrParser) consumePhrase() (phrase string, err error) { // atom // We actually parse dot-atom here to be more permissive // than what RFC 5322 specifies. - word, err = p.consumeAtom(true) + word, err = p.consumeAtom(true, true) } - // RFC 2047 encoded-word starts with =?, ends with ?=, and has two other ?s. - if err == nil && strings.HasPrefix(word, "=?") && strings.HasSuffix(word, "?=") && strings.Count(word, "?") == 4 { - word, err = decodeRFC2047Word(word) + if err == nil { + word, err = p.decodeRFC2047Word(word) } if err != nil { @@ -380,16 +417,16 @@ Loop: if i >= p.len() { return "", errors.New("mail: unclosed quoted-string") } - switch c := (*p)[i]; { + switch c := p.s[i]; { case c == '"': break Loop case c == '\\': if i+1 == p.len() { return "", errors.New("mail: unclosed quoted-string") } - qsb = append(qsb, (*p)[i+1]) + qsb = append(qsb, p.s[i+1]) i += 2 - case isQtext(c), c == ' ' || c == '\t': + case isQtext(c), c == ' ': // qtext (printable US-ASCII excluding " and \), or // FWS (almost; we're ignoring CRLF) qsb = append(qsb, c) @@ -398,20 +435,36 @@ Loop: return "", fmt.Errorf("mail: bad character in quoted-string: %q", c) } } - *p = (*p)[i+1:] + p.s = p.s[i+1:] + if len(qsb) == 0 { + return "", errors.New("mail: empty quoted-string") + } return string(qsb), nil } // consumeAtom parses an RFC 5322 atom at the start of p. // If dot is true, consumeAtom parses an RFC 5322 dot-atom instead. -func (p *addrParser) consumeAtom(dot bool) (atom string, err error) { +// If permissive is true, consumeAtom will not fail on +// leading/trailing/double dots in the atom (see golang.org/issue/4938). +func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) { if !isAtext(p.peek(), false) { return "", errors.New("mail: invalid string") } i := 1 - for ; i < p.len() && isAtext((*p)[i], dot); i++ { + for ; i < p.len() && isAtext(p.s[i], dot); i++ { + } + atom, p.s = string(p.s[:i]), p.s[i:] + if !permissive { + if strings.HasPrefix(atom, ".") { + return "", errors.New("mail: leading dot in atom") + } + if strings.Contains(atom, "..") { + return "", errors.New("mail: double dot in atom") + } + if strings.HasSuffix(atom, ".") { + return "", errors.New("mail: trailing dot in atom") + } } - atom, *p = string((*p)[:i]), (*p)[i:] return atom, nil } @@ -419,17 +472,17 @@ func (p *addrParser) consume(c byte) bool { if p.empty() || p.peek() != c { return false } - *p = (*p)[1:] + p.s = p.s[1:] return true } // skipSpace skips the leading space and tab characters. func (p *addrParser) skipSpace() { - *p = bytes.TrimLeft(*p, " \t") + p.s = strings.TrimLeft(p.s, " \t") } func (p *addrParser) peek() byte { - return (*p)[0] + return p.s[0] } func (p *addrParser) empty() bool { @@ -437,87 +490,37 @@ func (p *addrParser) empty() bool { } func (p *addrParser) len() int { - return len(*p) + return len(p.s) } -func decodeRFC2047Word(s string) (string, error) { - fields := strings.Split(s, "?") - if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" { - return "", errors.New("address not RFC 2047 encoded") - } - charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) - if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" { - return "", fmt.Errorf("charset not supported: %q", charset) +func (p *addrParser) decodeRFC2047Word(s string) (string, error) { + if p.dec != nil { + return p.dec.DecodeHeader(s) } - in := bytes.NewBufferString(fields[3]) - var r io.Reader - switch enc { - case "b": - r = base64.NewDecoder(base64.StdEncoding, in) - case "q": - r = qDecoder{r: in} - default: - return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc) + dec, err := rfc2047Decoder.Decode(s) + if err == nil { + return dec, nil } - dec, err := ioutil.ReadAll(r) - if err != nil { - return "", err + if _, ok := err.(charsetError); ok { + return s, err } - switch charset { - case "us-ascii": - b := new(bytes.Buffer) - for _, c := range dec { - if c >= 0x80 { - b.WriteRune(unicode.ReplacementChar) - } else { - b.WriteRune(rune(c)) - } - } - return b.String(), nil - case "iso-8859-1": - b := new(bytes.Buffer) - for _, c := range dec { - b.WriteRune(rune(c)) - } - return b.String(), nil - case "utf-8": - return string(dec), nil - } - panic("unreachable") + // Ignore invalid RFC 2047 encoded-word errors. + return s, nil } -type qDecoder struct { - r io.Reader - scratch [2]byte +var rfc2047Decoder = mime.WordDecoder{ + CharsetReader: func(charset string, input io.Reader) (io.Reader, error) { + return nil, charsetError(charset) + }, } -func (qd qDecoder) Read(p []byte) (n int, err error) { - // This method writes at most one byte into p. - if len(p) == 0 { - return 0, nil - } - if _, err := qd.r.Read(qd.scratch[:1]); err != nil { - return 0, err - } - switch c := qd.scratch[0]; { - case c == '=': - if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil { - return 0, err - } - x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64) - if err != nil { - return 0, fmt.Errorf("mail: invalid RFC 2047 encoding: %q", qd.scratch[:2]) - } - p[0] = byte(x) - case c == '_': - p[0] = ' ' - default: - p[0] = c - } - return 1, nil +type charsetError string + +func (e charsetError) Error() string { + return fmt.Sprintf("charset not supported: %q", string(e)) } var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + @@ -525,7 +528,7 @@ var atextChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "!#$%&'*+-/=?^_`{|}~") -// isAtext returns true if c is an RFC 5322 atext character. +// isAtext reports whether c is an RFC 5322 atext character. // If dot is true, period is included. func isAtext(c byte, dot bool) bool { if dot && c == '.' { @@ -534,7 +537,7 @@ func isAtext(c byte, dot bool) bool { return bytes.IndexByte(atextChars, c) >= 0 } -// isQtext returns true if c is an RFC 5322 qtext character. +// isQtext reports whether c is an RFC 5322 qtext character. func isQtext(c byte) bool { // Printable US-ASCII, excluding backslash or quote. if c == '\\' || c == '"' { @@ -543,13 +546,30 @@ func isQtext(c byte) bool { return '!' <= c && c <= '~' } -// isVchar returns true if c is an RFC 5322 VCHAR character. +// quoteString renders a string as a RFC5322 quoted-string. +func quoteString(s string) string { + var buf bytes.Buffer + buf.WriteByte('"') + for _, c := range s { + ch := byte(c) + if isQtext(ch) || isWSP(ch) { + buf.WriteByte(ch) + } else if isVchar(ch) { + buf.WriteByte('\\') + buf.WriteByte(ch) + } + } + buf.WriteByte('"') + return buf.String() +} + +// isVchar reports whether c is an RFC 5322 VCHAR character. func isVchar(c byte) bool { // Visible (printing) characters. return '!' <= c && c <= '~' } -// isWSP returns true if c is a WSP (white space). +// isWSP reports whether c is a WSP (white space). // WSP is a space or horizontal tab (RFC5234 Appendix B). func isWSP(c byte) bool { return c == ' ' || c == '\t' diff --git a/libgo/go/net/mail/message_test.go b/libgo/go/net/mail/message_test.go index 6ba48be04fa..1b422743f95 100644 --- a/libgo/go/net/mail/message_test.go +++ b/libgo/go/net/mail/message_test.go @@ -6,7 +6,9 @@ package mail import ( "bytes" + "io" "io/ioutil" + "mime" "reflect" "strings" "testing" @@ -278,6 +280,175 @@ func TestAddressParsing(t *testing.T) { } } +func TestAddressParser(t *testing.T) { + tests := []struct { + addrsStr string + exp []*Address + }{ + // Bare address + { + `jdoe@machine.example`, + []*Address{{ + Address: "jdoe@machine.example", + }}, + }, + // RFC 5322, Appendix A.1.1 + { + `John Doe <jdoe@machine.example>`, + []*Address{{ + Name: "John Doe", + Address: "jdoe@machine.example", + }}, + }, + // RFC 5322, Appendix A.1.2 + { + `"Joe Q. Public" <john.q.public@example.com>`, + []*Address{{ + Name: "Joe Q. Public", + Address: "john.q.public@example.com", + }}, + }, + { + `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`, + []*Address{ + { + Name: "Mary Smith", + Address: "mary@x.test", + }, + { + Address: "jdoe@example.org", + }, + { + Name: "Who?", + Address: "one@y.test", + }, + }, + }, + { + `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`, + []*Address{ + { + Address: "boss@nil.test", + }, + { + Name: `Giant; "Big" Box`, + Address: "sysservices@example.net", + }, + }, + }, + // RFC 2047 "Q"-encoded ISO-8859-1 address. + { + `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`, + []*Address{ + { + Name: `Jörg Doe`, + Address: "joerg@example.com", + }, + }, + }, + // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal. + { + `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`, + []*Address{ + { + Name: `Jorg Doe`, + Address: "joerg@example.com", + }, + }, + }, + // RFC 2047 "Q"-encoded ISO-8859-15 address. + { + `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg@example.com>`, + []*Address{ + { + Name: `Jörg Doe`, + Address: "joerg@example.com", + }, + }, + }, + // RFC 2047 "B"-encoded windows-1252 address. + { + `=?windows-1252?q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`, + []*Address{ + { + Name: `André Pirard`, + Address: "PIRARD@vm1.ulg.ac.be", + }, + }, + }, + // Custom example of RFC 2047 "B"-encoded ISO-8859-15 address. + { + `=?ISO-8859-15?B?SvZyZw==?= <joerg@example.com>`, + []*Address{ + { + Name: `Jörg`, + Address: "joerg@example.com", + }, + }, + }, + // Custom example of RFC 2047 "B"-encoded UTF-8 address. + { + `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`, + []*Address{ + { + Name: `Jörg`, + Address: "joerg@example.com", + }, + }, + }, + // Custom example with "." in name. For issue 4938 + { + `Asem H. <noreply@example.com>`, + []*Address{ + { + Name: `Asem H.`, + Address: "noreply@example.com", + }, + }, + }, + } + + ap := AddressParser{WordDecoder: &mime.WordDecoder{ + CharsetReader: func(charset string, input io.Reader) (io.Reader, error) { + in, err := ioutil.ReadAll(input) + if err != nil { + return nil, err + } + + switch charset { + case "iso-8859-15": + in = bytes.Replace(in, []byte("\xf6"), []byte("ö"), -1) + case "windows-1252": + in = bytes.Replace(in, []byte("\xe9"), []byte("é"), -1) + } + + return bytes.NewReader(in), nil + }, + }} + + for _, test := range tests { + if len(test.exp) == 1 { + addr, err := ap.Parse(test.addrsStr) + if err != nil { + t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err) + continue + } + if !reflect.DeepEqual([]*Address{addr}, test.exp) { + t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp) + } + } + + addrs, err := ap.ParseList(test.addrsStr) + if err != nil { + t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err) + continue + } + if !reflect.DeepEqual(addrs, test.exp) { + t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) + } + } +} + func TestAddressFormatting(t *testing.T) { tests := []struct { addr *Address @@ -287,6 +458,14 @@ func TestAddressFormatting(t *testing.T) { &Address{Address: "bob@example.com"}, "<bob@example.com>", }, + { // quoted local parts: RFC 5322, 3.4.1. and 3.2.4. + &Address{Address: `my@idiot@address@example.com`}, + `<"my@idiot@address"@example.com>`, + }, + { // quoted local parts + &Address{Address: ` @example.com`}, + `<" "@example.com>`, + }, { &Address{Name: "Bob", Address: "bob@example.com"}, `"Bob" <bob@example.com>`, @@ -304,6 +483,14 @@ func TestAddressFormatting(t *testing.T) { &Address{Name: "Böb Jacöb", Address: "bob@example.com"}, `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`, }, + { // https://golang.org/issue/12098 + &Address{Name: "Rob", Address: ""}, + `"Rob" <@>`, + }, + { // https://golang.org/issue/12098 + &Address{Name: "Rob", Address: "@"}, + `"Rob" <@>`, + }, } for _, test := range tests { s := test.addr.String() @@ -312,3 +499,90 @@ func TestAddressFormatting(t *testing.T) { } } } + +// Check if all valid addresses can be parsed, formatted and parsed again +func TestAddressParsingAndFormatting(t *testing.T) { + + // Should pass + tests := []string{ + `<Bob@example.com>`, + `<bob.bob@example.com>`, + `<".bob"@example.com>`, + `<" "@example.com>`, + `<some.mail-with-dash@example.com>`, + `<"dot.and space"@example.com>`, + `<"very.unusual.@.unusual.com"@example.com>`, + `<admin@mailserver1>`, + `<postmaster@localhost>`, + "<#!$%&'*+-/=?^_`{}|~@example.org>", + `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`, // escaped quotes + `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`, // escaped backslashes + `<"Abc\\@def"@example.com>`, + `<"Joe\\Blow"@example.com>`, + `<test1/test2=test3@example.com>`, + `<def!xyz%abc@example.com>`, + `<_somename@example.com>`, + `<joe@uk>`, + `<~@example.com>`, + `<"..."@test.com>`, + `<"john..doe"@example.com>`, + `<"john.doe."@example.com>`, + `<".john.doe"@example.com>`, + `<"."@example.com>`, + `<".."@example.com>`, + `<"0:"@0>`, + } + + for _, test := range tests { + addr, err := ParseAddress(test) + if err != nil { + t.Errorf("Couldn't parse address %s: %s", test, err.Error()) + continue + } + str := addr.String() + addr, err = ParseAddress(str) + if err != nil { + t.Errorf("ParseAddr(%q) error: %v", test, err) + continue + } + + if addr.String() != test { + t.Errorf("String() round-trip = %q; want %q", addr, test) + continue + } + + } + + // Should fail + badTests := []string{ + `<Abc.example.com>`, + `<A@b@c@example.com>`, + `<a"b(c)d,e:f;g<h>i[j\k]l@example.com>`, + `<just"not"right@example.com>`, + `<this is"not\allowed@example.com>`, + `<this\ still\"not\\allowed@example.com>`, + `<john..doe@example.com>`, + `<john.doe@example..com>`, + `<john.doe@example..com>`, + `<john.doe.@example.com>`, + `<john.doe.@.example.com>`, + `<.john.doe@example.com>`, + `<@example.com>`, + `<.@example.com>`, + `<test@.>`, + `< @example.com>`, + `<""test""blah""@example.com>`, + `<""@0>`, + "<\"\t0\"@0>", + } + + for _, test := range badTests { + _, err := ParseAddress(test) + if err == nil { + t.Errorf("Should have failed to parse address: %s", test) + continue + } + + } + +} diff --git a/libgo/go/net/main_cloexec_test.go b/libgo/go/net/main_cloexec_test.go new file mode 100644 index 00000000000..79038195859 --- /dev/null +++ b/libgo/go/net/main_cloexec_test.go @@ -0,0 +1,25 @@ +// Copyright 2015 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 freebsd linux + +package net + +func init() { + extraTestHookInstallers = append(extraTestHookInstallers, installAccept4TestHook) + extraTestHookUninstallers = append(extraTestHookUninstallers, uninstallAccept4TestHook) +} + +var ( + // Placeholders for saving original socket system calls. + origAccept4 = accept4Func +) + +func installAccept4TestHook() { + accept4Func = sw.Accept4 +} + +func uninstallAccept4TestHook() { + accept4Func = origAccept4 +} diff --git a/libgo/go/net/main_plan9_test.go b/libgo/go/net/main_plan9_test.go new file mode 100644 index 00000000000..94501cada9a --- /dev/null +++ b/libgo/go/net/main_plan9_test.go @@ -0,0 +1,15 @@ +// Copyright 2015 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 + +func installTestHooks() {} + +func uninstallTestHooks() {} + +func forceCloseSockets() {} + +func enableSocketConnect() {} + +func disableSocketConnect(network string) {} diff --git a/libgo/go/net/main_posix_test.go b/libgo/go/net/main_posix_test.go new file mode 100644 index 00000000000..ead311c3cdd --- /dev/null +++ b/libgo/go/net/main_posix_test.go @@ -0,0 +1,50 @@ +// Copyright 2015 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 !plan9 + +package net + +import ( + "net/internal/socktest" + "strings" + "syscall" +) + +func enableSocketConnect() { + sw.Set(socktest.FilterConnect, nil) +} + +func disableSocketConnect(network string) { + ss := strings.Split(network, ":") + sw.Set(socktest.FilterConnect, func(so *socktest.Status) (socktest.AfterFilter, error) { + switch ss[0] { + case "tcp4": + if so.Cookie.Family() == syscall.AF_INET && so.Cookie.Type() == syscall.SOCK_STREAM { + return nil, syscall.EHOSTUNREACH + } + case "udp4": + if so.Cookie.Family() == syscall.AF_INET && so.Cookie.Type() == syscall.SOCK_DGRAM { + return nil, syscall.EHOSTUNREACH + } + case "ip4": + if so.Cookie.Family() == syscall.AF_INET && so.Cookie.Type() == syscall.SOCK_RAW { + return nil, syscall.EHOSTUNREACH + } + case "tcp6": + if so.Cookie.Family() == syscall.AF_INET6 && so.Cookie.Type() == syscall.SOCK_STREAM { + return nil, syscall.EHOSTUNREACH + } + case "udp6": + if so.Cookie.Family() == syscall.AF_INET6 && so.Cookie.Type() == syscall.SOCK_DGRAM { + return nil, syscall.EHOSTUNREACH + } + case "ip6": + if so.Cookie.Family() == syscall.AF_INET6 && so.Cookie.Type() == syscall.SOCK_RAW { + return nil, syscall.EHOSTUNREACH + } + } + return nil, nil + }) +} diff --git a/libgo/go/net/main_test.go b/libgo/go/net/main_test.go new file mode 100644 index 00000000000..f3f8b1a9002 --- /dev/null +++ b/libgo/go/net/main_test.go @@ -0,0 +1,204 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package net + +import ( + "flag" + "fmt" + "net/internal/socktest" + "os" + "runtime" + "sort" + "strings" + "sync" + "testing" +) + +var ( + sw socktest.Switch + + // uninstallTestHooks runs just before a run of benchmarks. + testHookUninstaller sync.Once +) + +var ( + testDNSFlood = flag.Bool("dnsflood", false, "whether to test DNS query flooding") + + testExternal = flag.Bool("external", true, "allow use of external networks during long test") + + // If external IPv4 connectivity exists, we can try dialing + // non-node/interface local scope IPv4 addresses. + // On Windows, Lookup APIs may not return IPv4-related + // resource records when a node has no external IPv4 + // connectivity. + testIPv4 = flag.Bool("ipv4", true, "assume external IPv4 connectivity exists") + + // If external IPv6 connectivity exists, we can try dialing + // non-node/interface local scope IPv6 addresses. + // On Windows, Lookup APIs may not return IPv6-related + // resource records when a node has no external IPv6 + // connectivity. + testIPv6 = flag.Bool("ipv6", false, "assume external IPv6 connectivity exists") +) + +func TestMain(m *testing.M) { + setupTestData() + installTestHooks() + + st := m.Run() + + testHookUninstaller.Do(uninstallTestHooks) + if testing.Verbose() { + printRunningGoroutines() + printInflightSockets() + printSocketStats() + } + forceCloseSockets() + os.Exit(st) +} + +type ipv6LinkLocalUnicastTest struct { + network, address string + nameLookup bool +} + +var ( + ipv6LinkLocalUnicastTCPTests []ipv6LinkLocalUnicastTest + ipv6LinkLocalUnicastUDPTests []ipv6LinkLocalUnicastTest +) + +func setupTestData() { + if supportsIPv4 { + resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{ + {"tcp", "localhost:1", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 1}, nil}, + {"tcp4", "localhost:2", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 2}, nil}, + }...) + resolveUDPAddrTests = append(resolveUDPAddrTests, []resolveUDPAddrTest{ + {"udp", "localhost:1", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 1}, nil}, + {"udp4", "localhost:2", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 2}, nil}, + }...) + resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{ + {"ip", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, + {"ip4", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, + }...) + } + + if supportsIPv6 { + resolveTCPAddrTests = append(resolveTCPAddrTests, resolveTCPAddrTest{"tcp6", "localhost:3", &TCPAddr{IP: IPv6loopback, Port: 3}, nil}) + resolveUDPAddrTests = append(resolveUDPAddrTests, resolveUDPAddrTest{"udp6", "localhost:3", &UDPAddr{IP: IPv6loopback, Port: 3}, nil}) + resolveIPAddrTests = append(resolveIPAddrTests, resolveIPAddrTest{"ip6", "localhost", &IPAddr{IP: IPv6loopback}, nil}) + } + + ifi := loopbackInterface() + if ifi != nil { + index := fmt.Sprintf("%v", ifi.Index) + resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{ + {"tcp6", "[fe80::1%" + ifi.Name + "]:1", &TCPAddr{IP: ParseIP("fe80::1"), Port: 1, Zone: zoneToString(ifi.Index)}, nil}, + {"tcp6", "[fe80::1%" + index + "]:2", &TCPAddr{IP: ParseIP("fe80::1"), Port: 2, Zone: index}, nil}, + }...) + resolveUDPAddrTests = append(resolveUDPAddrTests, []resolveUDPAddrTest{ + {"udp6", "[fe80::1%" + ifi.Name + "]:1", &UDPAddr{IP: ParseIP("fe80::1"), Port: 1, Zone: zoneToString(ifi.Index)}, nil}, + {"udp6", "[fe80::1%" + index + "]:2", &UDPAddr{IP: ParseIP("fe80::1"), Port: 2, Zone: index}, nil}, + }...) + resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{ + {"ip6", "fe80::1%" + ifi.Name, &IPAddr{IP: ParseIP("fe80::1"), Zone: zoneToString(ifi.Index)}, nil}, + {"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil}, + }...) + } + + addr := ipv6LinkLocalUnicastAddr(ifi) + if addr != "" { + if runtime.GOOS != "dragonfly" { + ipv6LinkLocalUnicastTCPTests = append(ipv6LinkLocalUnicastTCPTests, []ipv6LinkLocalUnicastTest{ + {"tcp", "[" + addr + "%" + ifi.Name + "]:0", false}, + }...) + ipv6LinkLocalUnicastUDPTests = append(ipv6LinkLocalUnicastUDPTests, []ipv6LinkLocalUnicastTest{ + {"udp", "[" + addr + "%" + ifi.Name + "]:0", false}, + }...) + } + ipv6LinkLocalUnicastTCPTests = append(ipv6LinkLocalUnicastTCPTests, []ipv6LinkLocalUnicastTest{ + {"tcp6", "[" + addr + "%" + ifi.Name + "]:0", false}, + }...) + ipv6LinkLocalUnicastUDPTests = append(ipv6LinkLocalUnicastUDPTests, []ipv6LinkLocalUnicastTest{ + {"udp6", "[" + addr + "%" + ifi.Name + "]:0", false}, + }...) + switch runtime.GOOS { + case "darwin", "dragonfly", "freebsd", "openbsd", "netbsd": + ipv6LinkLocalUnicastTCPTests = append(ipv6LinkLocalUnicastTCPTests, []ipv6LinkLocalUnicastTest{ + {"tcp", "[localhost%" + ifi.Name + "]:0", true}, + {"tcp6", "[localhost%" + ifi.Name + "]:0", true}, + }...) + ipv6LinkLocalUnicastUDPTests = append(ipv6LinkLocalUnicastUDPTests, []ipv6LinkLocalUnicastTest{ + {"udp", "[localhost%" + ifi.Name + "]:0", true}, + {"udp6", "[localhost%" + ifi.Name + "]:0", true}, + }...) + case "linux": + ipv6LinkLocalUnicastTCPTests = append(ipv6LinkLocalUnicastTCPTests, []ipv6LinkLocalUnicastTest{ + {"tcp", "[ip6-localhost%" + ifi.Name + "]:0", true}, + {"tcp6", "[ip6-localhost%" + ifi.Name + "]:0", true}, + }...) + ipv6LinkLocalUnicastUDPTests = append(ipv6LinkLocalUnicastUDPTests, []ipv6LinkLocalUnicastTest{ + {"udp", "[ip6-localhost%" + ifi.Name + "]:0", true}, + {"udp6", "[ip6-localhost%" + ifi.Name + "]:0", true}, + }...) + } + } +} + +func printRunningGoroutines() { + gss := runningGoroutines() + if len(gss) == 0 { + return + } + fmt.Fprintf(os.Stderr, "Running goroutines:\n") + for _, gs := range gss { + fmt.Fprintf(os.Stderr, "%v\n", gs) + } + fmt.Fprintf(os.Stderr, "\n") +} + +// runningGoroutines returns a list of remaining goroutines. +func runningGoroutines() []string { + var gss []string + b := make([]byte, 2<<20) + b = b[:runtime.Stack(b, true)] + for _, s := range strings.Split(string(b), "\n\n") { + ss := strings.SplitN(s, "\n", 2) + if len(ss) != 2 { + continue + } + stack := strings.TrimSpace(ss[1]) + if !strings.Contains(stack, "created by net") { + continue + } + gss = append(gss, stack) + } + sort.Strings(gss) + return gss +} + +func printInflightSockets() { + sos := sw.Sockets() + if len(sos) == 0 { + return + } + fmt.Fprintf(os.Stderr, "Inflight sockets:\n") + for s, so := range sos { + fmt.Fprintf(os.Stderr, "%v: %v\n", s, so) + } + fmt.Fprintf(os.Stderr, "\n") +} + +func printSocketStats() { + sts := sw.Stats() + if len(sts) == 0 { + return + } + fmt.Fprintf(os.Stderr, "Socket statistical information:\n") + for _, st := range sts { + fmt.Fprintf(os.Stderr, "%v\n", st) + } + fmt.Fprintf(os.Stderr, "\n") +} diff --git a/libgo/go/net/main_unix_test.go b/libgo/go/net/main_unix_test.go new file mode 100644 index 00000000000..bfb4cd0065c --- /dev/null +++ b/libgo/go/net/main_unix_test.go @@ -0,0 +1,52 @@ +// Copyright 2015 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 dragonfly freebsd linux nacl netbsd openbsd solaris + +package net + +var ( + // Placeholders for saving original socket system calls. + origSocket = socketFunc + origClose = closeFunc + origConnect = connectFunc + origListen = listenFunc + origAccept = acceptFunc + origGetsockoptInt = getsockoptIntFunc + + extraTestHookInstallers []func() + extraTestHookUninstallers []func() +) + +func installTestHooks() { + socketFunc = sw.Socket + closeFunc = sw.Close + connectFunc = sw.Connect + listenFunc = sw.Listen + acceptFunc = sw.Accept + getsockoptIntFunc = sw.GetsockoptInt + + for _, fn := range extraTestHookInstallers { + fn() + } +} + +func uninstallTestHooks() { + socketFunc = origSocket + closeFunc = origClose + connectFunc = origConnect + listenFunc = origListen + acceptFunc = origAccept + getsockoptIntFunc = origGetsockoptInt + + for _, fn := range extraTestHookUninstallers { + fn() + } +} + +func forceCloseSockets() { + for s := range sw.Sockets() { + closeFunc(s) + } +} diff --git a/libgo/go/net/main_windows_test.go b/libgo/go/net/main_windows_test.go new file mode 100644 index 00000000000..2d829743ec5 --- /dev/null +++ b/libgo/go/net/main_windows_test.go @@ -0,0 +1,36 @@ +// Copyright 2015 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 + +var ( + // Placeholders for saving original socket system calls. + origSocket = socketFunc + origClosesocket = closeFunc + origConnect = connectFunc + origConnectEx = connectExFunc + origListen = listenFunc +) + +func installTestHooks() { + socketFunc = sw.Socket + closeFunc = sw.Closesocket + connectFunc = sw.Connect + connectExFunc = sw.ConnectEx + listenFunc = sw.Listen +} + +func uninstallTestHooks() { + socketFunc = origSocket + closeFunc = origClosesocket + connectFunc = origConnect + connectExFunc = origConnectEx + listenFunc = origListen +} + +func forceCloseSockets() { + for s := range sw.Sockets() { + closeFunc(s) + } +} diff --git a/libgo/go/net/mockicmp_test.go b/libgo/go/net/mockicmp_test.go deleted file mode 100644 index e742365ea03..00000000000 --- a/libgo/go/net/mockicmp_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package net - -import "errors" - -const ( - icmpv4EchoRequest = 8 - icmpv4EchoReply = 0 - icmpv6EchoRequest = 128 - icmpv6EchoReply = 129 -) - -// icmpMessage represents an ICMP message. -type icmpMessage struct { - Type int // type - Code int // code - Checksum int // checksum - Body icmpMessageBody // body -} - -// icmpMessageBody represents an ICMP message body. -type icmpMessageBody interface { - Len() int - Marshal() ([]byte, error) -} - -// Marshal returns the binary enconding of the ICMP echo request or -// reply message m. -func (m *icmpMessage) Marshal() ([]byte, error) { - b := []byte{byte(m.Type), byte(m.Code), 0, 0} - if m.Body != nil && m.Body.Len() != 0 { - mb, err := m.Body.Marshal() - if err != nil { - return nil, err - } - b = append(b, mb...) - } - switch m.Type { - case icmpv6EchoRequest, icmpv6EchoReply: - return b, nil - } - csumcv := len(b) - 1 // checksum coverage - s := uint32(0) - for i := 0; i < csumcv; i += 2 { - s += uint32(b[i+1])<<8 | uint32(b[i]) - } - if csumcv&1 == 0 { - s += uint32(b[csumcv]) - } - 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) - b[3] ^= byte(^s >> 8) - return b, nil -} - -// parseICMPMessage parses b as an ICMP message. -func parseICMPMessage(b []byte) (*icmpMessage, error) { - msglen := len(b) - if msglen < 4 { - return nil, errors.New("message too short") - } - m := &icmpMessage{Type: int(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])} - if msglen > 4 { - var err error - switch m.Type { - case icmpv4EchoRequest, icmpv4EchoReply, icmpv6EchoRequest, icmpv6EchoReply: - m.Body, err = parseICMPEcho(b[4:]) - if err != nil { - return nil, err - } - } - } - return m, nil -} - -// imcpEcho represenets an ICMP echo request or reply message body. -type icmpEcho struct { - ID int // identifier - Seq int // sequence number - Data []byte // data -} - -func (p *icmpEcho) Len() int { - if p == nil { - return 0 - } - return 4 + len(p.Data) -} - -// Marshal returns the binary enconding of the ICMP echo request or -// reply message body p. -func (p *icmpEcho) Marshal() ([]byte, error) { - b := make([]byte, 4+len(p.Data)) - b[0], b[1] = byte(p.ID>>8), byte(p.ID) - b[2], b[3] = byte(p.Seq>>8), byte(p.Seq) - copy(b[4:], p.Data) - return b, nil -} - -// parseICMPEcho parses b as an ICMP echo request or reply message -// body. -func parseICMPEcho(b []byte) (*icmpEcho, error) { - bodylen := len(b) - p := &icmpEcho{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])} - if bodylen > 4 { - p.Data = make([]byte, bodylen-4) - copy(p.Data, b[4:]) - } - return p, nil -} diff --git a/libgo/go/net/mockserver_test.go b/libgo/go/net/mockserver_test.go index 68ded5d7577..dd6f4df3b9c 100644 --- a/libgo/go/net/mockserver_test.go +++ b/libgo/go/net/mockserver_test.go @@ -4,11 +4,123 @@ package net -import "sync" +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "sync" + "testing" + "time" +) + +// testUnixAddr uses ioutil.TempFile to get a name that is unique. +// It also uses /tmp directory in case it is prohibited to create UNIX +// sockets in TMPDIR. +func testUnixAddr() string { + f, err := ioutil.TempFile("", "go-nettest") + if err != nil { + panic(err) + } + addr := f.Name() + f.Close() + os.Remove(addr) + return addr +} + +func newLocalListener(network string) (Listener, error) { + switch network { + case "tcp", "tcp4", "tcp6": + if supportsIPv4 { + return Listen("tcp4", "127.0.0.1:0") + } + if supportsIPv6 { + return Listen("tcp6", "[::1]:0") + } + case "unix", "unixpacket": + return Listen(network, testUnixAddr()) + } + return nil, fmt.Errorf("%s is not supported", network) +} + +func newDualStackListener() (lns []*TCPListener, err error) { + var args = []struct { + network string + TCPAddr + }{ + {"tcp4", TCPAddr{IP: IPv4(127, 0, 0, 1)}}, + {"tcp6", TCPAddr{IP: IPv6loopback}}, + } + for i := 0; i < 64; i++ { + var port int + var lns []*TCPListener + for _, arg := range args { + arg.TCPAddr.Port = port + ln, err := ListenTCP(arg.network, &arg.TCPAddr) + if err != nil { + continue + } + port = ln.Addr().(*TCPAddr).Port + lns = append(lns, ln) + } + if len(lns) != len(args) { + for _, ln := range lns { + ln.Close() + } + continue + } + return lns, nil + } + return nil, errors.New("no dualstack port available") +} + +type localServer struct { + lnmu sync.RWMutex + Listener + done chan bool // signal that indicates server stopped +} + +func (ls *localServer) buildup(handler func(*localServer, Listener)) error { + go func() { + handler(ls, ls.Listener) + close(ls.done) + }() + return nil +} + +func (ls *localServer) teardown() error { + ls.lnmu.Lock() + if ls.Listener != nil { + network := ls.Listener.Addr().Network() + address := ls.Listener.Addr().String() + ls.Listener.Close() + <-ls.done + ls.Listener = nil + switch network { + case "unix", "unixpacket": + os.Remove(address) + } + } + ls.lnmu.Unlock() + return nil +} + +func newLocalServer(network string) (*localServer, error) { + ln, err := newLocalListener(network) + if err != nil { + return nil, err + } + return &localServer{Listener: ln, done: make(chan bool)}, nil +} type streamListener struct { - net, addr string - ln Listener + network, address string + Listener + done chan bool // signal that indicates server stopped +} + +func (sl *streamListener) newLocalServer() (*localServer, error) { + return &localServer{Listener: sl.Listener, done: make(chan bool)}, nil } type dualStackServer struct { @@ -20,9 +132,12 @@ type dualStackServer struct { cs []Conn // established connections at the passive open side } -func (dss *dualStackServer) buildup(server func(*dualStackServer, Listener)) error { +func (dss *dualStackServer) buildup(handler func(*dualStackServer, Listener)) error { for i := range dss.lns { - go server(dss, dss.lns[i].ln) + go func(i int) { + handler(dss, dss.lns[i].Listener) + close(dss.lns[i].done) + }(i) } return nil } @@ -34,12 +149,13 @@ func (dss *dualStackServer) putConn(c Conn) error { return nil } -func (dss *dualStackServer) teardownNetwork(net string) error { +func (dss *dualStackServer) teardownNetwork(network string) error { dss.lnmu.Lock() for i := range dss.lns { - if net == dss.lns[i].net && dss.lns[i].ln != nil { - dss.lns[i].ln.Close() - dss.lns[i].ln = nil + if network == dss.lns[i].network && dss.lns[i].Listener != nil { + dss.lns[i].Listener.Close() + <-dss.lns[i].done + dss.lns[i].Listener = nil } } dss.lnmu.Unlock() @@ -49,15 +165,18 @@ func (dss *dualStackServer) teardownNetwork(net string) error { func (dss *dualStackServer) teardown() error { dss.lnmu.Lock() for i := range dss.lns { - if dss.lns[i].ln != nil { - dss.lns[i].ln.Close() + if dss.lns[i].Listener != nil { + dss.lns[i].Listener.Close() + <-dss.lns[i].done } } + dss.lns = dss.lns[:0] dss.lnmu.Unlock() dss.cmu.Lock() for _, c := range dss.cs { c.Close() } + dss.cs = dss.cs[:0] dss.cmu.Unlock() return nil } @@ -65,18 +184,333 @@ func (dss *dualStackServer) teardown() error { func newDualStackServer(lns []streamListener) (*dualStackServer, error) { dss := &dualStackServer{lns: lns, port: "0"} for i := range dss.lns { - ln, err := Listen(dss.lns[i].net, dss.lns[i].addr+":"+dss.port) + ln, err := Listen(dss.lns[i].network, JoinHostPort(dss.lns[i].address, dss.port)) if err != nil { - dss.teardown() + for _, ln := range dss.lns[:i] { + ln.Listener.Close() + } return nil, err } - dss.lns[i].ln = ln + dss.lns[i].Listener = ln + dss.lns[i].done = make(chan bool) if dss.port == "0" { if _, dss.port, err = SplitHostPort(ln.Addr().String()); err != nil { - dss.teardown() + for _, ln := range dss.lns { + ln.Listener.Close() + } return nil, err } } } return dss, nil } + +func transponder(ln Listener, ch chan<- error) { + defer close(ch) + + switch ln := ln.(type) { + case *TCPListener: + ln.SetDeadline(time.Now().Add(someTimeout)) + case *UnixListener: + ln.SetDeadline(time.Now().Add(someTimeout)) + } + c, err := ln.Accept() + if err != nil { + if perr := parseAcceptError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + defer c.Close() + + network := ln.Addr().Network() + if c.LocalAddr().Network() != network || c.LocalAddr().Network() != network { + ch <- fmt.Errorf("got %v->%v; expected %v->%v", c.LocalAddr().Network(), c.RemoteAddr().Network(), network, network) + return + } + c.SetDeadline(time.Now().Add(someTimeout)) + c.SetReadDeadline(time.Now().Add(someTimeout)) + c.SetWriteDeadline(time.Now().Add(someTimeout)) + + b := make([]byte, 256) + n, err := c.Read(b) + if err != nil { + if perr := parseReadError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + if _, err := c.Write(b[:n]); err != nil { + if perr := parseWriteError(err); perr != nil { + ch <- perr + } + ch <- err + return + } +} + +func transceiver(c Conn, wb []byte, ch chan<- error) { + defer close(ch) + + c.SetDeadline(time.Now().Add(someTimeout)) + c.SetReadDeadline(time.Now().Add(someTimeout)) + c.SetWriteDeadline(time.Now().Add(someTimeout)) + + n, err := c.Write(wb) + if err != nil { + if perr := parseWriteError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + if n != len(wb) { + ch <- fmt.Errorf("wrote %d; want %d", n, len(wb)) + } + rb := make([]byte, len(wb)) + n, err = c.Read(rb) + if err != nil { + if perr := parseReadError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + if n != len(wb) { + ch <- fmt.Errorf("read %d; want %d", n, len(wb)) + } +} + +func timeoutReceiver(c Conn, d, min, max time.Duration, ch chan<- error) { + var err error + defer func() { ch <- err }() + + t0 := time.Now() + if err = c.SetReadDeadline(time.Now().Add(d)); err != nil { + return + } + b := make([]byte, 256) + var n int + n, err = c.Read(b) + t1 := time.Now() + if n != 0 || err == nil || !err.(Error).Timeout() { + err = fmt.Errorf("Read did not return (0, timeout): (%d, %v)", n, err) + return + } + if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() { + err = fmt.Errorf("Read took %s; expected %s", dt, d) + return + } +} + +func timeoutTransmitter(c Conn, d, min, max time.Duration, ch chan<- error) { + var err error + defer func() { ch <- err }() + + t0 := time.Now() + if err = c.SetWriteDeadline(time.Now().Add(d)); err != nil { + return + } + var n int + for { + n, err = c.Write([]byte("TIMEOUT TRANSMITTER")) + if err != nil { + break + } + } + t1 := time.Now() + if err == nil || !err.(Error).Timeout() { + err = fmt.Errorf("Write did not return (any, timeout): (%d, %v)", n, err) + return + } + if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() { + err = fmt.Errorf("Write took %s; expected %s", dt, d) + return + } +} + +func newLocalPacketListener(network string) (PacketConn, error) { + switch network { + case "udp", "udp4", "udp6": + if supportsIPv4 { + return ListenPacket("udp4", "127.0.0.1:0") + } + if supportsIPv6 { + return ListenPacket("udp6", "[::1]:0") + } + case "unixgram": + return ListenPacket(network, testUnixAddr()) + } + return nil, fmt.Errorf("%s is not supported", network) +} + +func newDualStackPacketListener() (cs []*UDPConn, err error) { + var args = []struct { + network string + UDPAddr + }{ + {"udp4", UDPAddr{IP: IPv4(127, 0, 0, 1)}}, + {"udp6", UDPAddr{IP: IPv6loopback}}, + } + for i := 0; i < 64; i++ { + var port int + var cs []*UDPConn + for _, arg := range args { + arg.UDPAddr.Port = port + c, err := ListenUDP(arg.network, &arg.UDPAddr) + if err != nil { + continue + } + port = c.LocalAddr().(*UDPAddr).Port + cs = append(cs, c) + } + if len(cs) != len(args) { + for _, c := range cs { + c.Close() + } + continue + } + return cs, nil + } + return nil, errors.New("no dualstack port available") +} + +type localPacketServer struct { + pcmu sync.RWMutex + PacketConn + done chan bool // signal that indicates server stopped +} + +func (ls *localPacketServer) buildup(handler func(*localPacketServer, PacketConn)) error { + go func() { + handler(ls, ls.PacketConn) + close(ls.done) + }() + return nil +} + +func (ls *localPacketServer) teardown() error { + ls.pcmu.Lock() + if ls.PacketConn != nil { + network := ls.PacketConn.LocalAddr().Network() + address := ls.PacketConn.LocalAddr().String() + ls.PacketConn.Close() + <-ls.done + ls.PacketConn = nil + switch network { + case "unixgram": + os.Remove(address) + } + } + ls.pcmu.Unlock() + return nil +} + +func newLocalPacketServer(network string) (*localPacketServer, error) { + c, err := newLocalPacketListener(network) + if err != nil { + return nil, err + } + return &localPacketServer{PacketConn: c, done: make(chan bool)}, nil +} + +type packetListener struct { + PacketConn +} + +func (pl *packetListener) newLocalServer() (*localPacketServer, error) { + return &localPacketServer{PacketConn: pl.PacketConn, done: make(chan bool)}, nil +} + +func packetTransponder(c PacketConn, ch chan<- error) { + defer close(ch) + + c.SetDeadline(time.Now().Add(someTimeout)) + c.SetReadDeadline(time.Now().Add(someTimeout)) + c.SetWriteDeadline(time.Now().Add(someTimeout)) + + b := make([]byte, 256) + n, peer, err := c.ReadFrom(b) + if err != nil { + if perr := parseReadError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + if peer == nil { // for connected-mode sockets + switch c.LocalAddr().Network() { + case "udp": + peer, err = ResolveUDPAddr("udp", string(b[:n])) + case "unixgram": + peer, err = ResolveUnixAddr("unixgram", string(b[:n])) + } + if err != nil { + ch <- err + return + } + } + if _, err := c.WriteTo(b[:n], peer); err != nil { + if perr := parseWriteError(err); perr != nil { + ch <- perr + } + ch <- err + return + } +} + +func packetTransceiver(c PacketConn, wb []byte, dst Addr, ch chan<- error) { + defer close(ch) + + c.SetDeadline(time.Now().Add(someTimeout)) + c.SetReadDeadline(time.Now().Add(someTimeout)) + c.SetWriteDeadline(time.Now().Add(someTimeout)) + + n, err := c.WriteTo(wb, dst) + if err != nil { + if perr := parseWriteError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + if n != len(wb) { + ch <- fmt.Errorf("wrote %d; want %d", n, len(wb)) + } + rb := make([]byte, len(wb)) + n, _, err = c.ReadFrom(rb) + if err != nil { + if perr := parseReadError(err); perr != nil { + ch <- perr + } + ch <- err + return + } + if n != len(wb) { + ch <- fmt.Errorf("read %d; want %d", n, len(wb)) + } +} + +func timeoutPacketReceiver(c PacketConn, d, min, max time.Duration, ch chan<- error) { + var err error + defer func() { ch <- err }() + + t0 := time.Now() + if err = c.SetReadDeadline(time.Now().Add(d)); err != nil { + return + } + b := make([]byte, 256) + var n int + n, _, err = c.ReadFrom(b) + t1 := time.Now() + if n != 0 || err == nil || !err.(Error).Timeout() { + err = fmt.Errorf("ReadFrom did not return (0, timeout): (%d, %v)", n, err) + return + } + if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() { + err = fmt.Errorf("ReadFrom took %s; expected %s", dt, d) + return + } +} diff --git a/libgo/go/net/multicast_test.go b/libgo/go/net/multicast_test.go deleted file mode 100644 index 5f253f44a45..00000000000 --- a/libgo/go/net/multicast_test.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2011 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 ( - "fmt" - "os" - "runtime" - "testing" -) - -var ipv4MulticastListenerTests = []struct { - net string - gaddr *UDPAddr // see RFC 4727 -}{ - {"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}}, - - {"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}}, -} - -// TestIPv4MulticastListener tests both single and double listen to a -// test listener with same address family, same group address and same -// port. -func TestIPv4MulticastListener(t *testing.T) { - switch runtime.GOOS { - case "android", "nacl", "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - case "solaris": - t.Skipf("skipping test on solaris, see issue 7399") - } - - closer := func(cs []*UDPConn) { - for _, c := range cs { - if c != nil { - c.Close() - } - } - } - - for _, ifi := range []*Interface{loopbackInterface(), nil} { - // Note that multicast interface assignment by system - // is not recommended because it usually relies on - // routing stuff for finding out an appropriate - // nexthop containing both network and link layer - // adjacencies. - if ifi == nil && !*testExternal { - continue - } - for _, tt := range ipv4MulticastListenerTests { - var err error - cs := make([]*UDPConn, 2) - if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { - t.Fatalf("First ListenMulticastUDP on %v failed: %v", ifi, err) - } - if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil { - closer(cs) - t.Fatal(err) - } - if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { - closer(cs) - t.Fatalf("Second ListenMulticastUDP on %v failed: %v", ifi, err) - } - if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil { - closer(cs) - t.Fatal(err) - } - closer(cs) - } - } -} - -var ipv6MulticastListenerTests = []struct { - net string - gaddr *UDPAddr // see RFC 4727 -}{ - {"udp", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}}, - {"udp", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}}, - {"udp", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}}, - {"udp", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}}, - {"udp", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}}, - {"udp", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}}, - - {"udp6", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}}, - {"udp6", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}}, - {"udp6", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}}, - {"udp6", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}}, - {"udp6", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}}, - {"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}}, -} - -// TestIPv6MulticastListener tests both single and double listen to a -// test listener with same address family, same group address and same -// port. -func TestIPv6MulticastListener(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - case "solaris": - t.Skipf("skipping test on solaris, see issue 7399") - } - if !supportsIPv6 { - t.Skip("ipv6 is not supported") - } - if os.Getuid() != 0 { - t.Skip("skipping test; must be root") - } - - closer := func(cs []*UDPConn) { - for _, c := range cs { - if c != nil { - c.Close() - } - } - } - - for _, ifi := range []*Interface{loopbackInterface(), nil} { - // Note that multicast interface assignment by system - // is not recommended because it usually relies on - // routing stuff for finding out an appropriate - // nexthop containing both network and link layer - // adjacencies. - if ifi == nil && (!*testExternal || !*testIPv6) { - continue - } - for _, tt := range ipv6MulticastListenerTests { - var err error - cs := make([]*UDPConn, 2) - if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { - t.Fatalf("First ListenMulticastUDP on %v failed: %v", ifi, err) - } - if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil { - closer(cs) - t.Fatal(err) - } - if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil { - closer(cs) - t.Fatalf("Second ListenMulticastUDP on %v failed: %v", ifi, err) - } - if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil { - closer(cs) - t.Fatal(err) - } - closer(cs) - } - } -} - -func checkMulticastListener(c *UDPConn, ip IP) error { - if ok, err := multicastRIBContains(ip); err != nil { - return err - } else if !ok { - return fmt.Errorf("%q not found in multicast RIB", ip.String()) - } - la := c.LocalAddr() - if la, ok := la.(*UDPAddr); !ok || la.Port == 0 { - return fmt.Errorf("got %v; expected a proper address with non-zero port number", la) - } - return nil -} - -func multicastRIBContains(ip IP) (bool, error) { - switch runtime.GOOS { - case "dragonfly", "netbsd", "openbsd", "plan9", "solaris", "windows": - return true, nil // not implemented yet - case "linux": - if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" { - return true, nil // not implemented yet - } - } - ift, err := Interfaces() - if err != nil { - return false, err - } - for _, ifi := range ift { - ifmat, err := ifi.MulticastAddrs() - if err != nil { - return false, err - } - for _, ifma := range ifmat { - if ifma.(*IPAddr).IP.Equal(ip) { - return true, nil - } - } - } - return false, nil -} diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index cb31af5e347..6e84c3a100e 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -35,12 +35,49 @@ The Listen function creates servers: } go handleConnection(conn) } + +Name Resolution + +The method for resolving domain names, whether indirectly with functions like Dial +or directly with functions like LookupHost and LookupAddr, varies by operating system. + +On Unix systems, the resolver has two options for resolving names. +It can use a pure Go resolver that sends DNS requests directly to the servers +listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C +library routines such as getaddrinfo and getnameinfo. + +By default the pure Go resolver is used, because a blocked DNS request consumes +only a goroutine, while a blocked C call consumes an operating system thread. +When cgo is available, the cgo-based resolver is used instead under a variety of +conditions: on systems that do not let programs make direct DNS requests (OS X), +when the LOCALDOMAIN environment variable is present (even if empty), +when the RES_OPTIONS or HOSTALIASES environment variable is non-empty, +when the ASR_CONFIG environment variable is non-empty (OpenBSD only), +when /etc/resolv.conf or /etc/nsswitch.conf specify the use of features that the +Go resolver does not implement, and when the name being looked up ends in .local +or is an mDNS name. + +The resolver decision can be overridden by setting the netdns value of the +GODEBUG environment variable (see package runtime) to go or cgo, as in: + + export GODEBUG=netdns=go # force pure Go resolver + export GODEBUG=netdns=cgo # force cgo resolver + +The decision can also be forced while building the Go source tree +by setting the netgo or netcgo build tag. + +A numeric netdns setting, as in GODEBUG=netdns=1, causes the resolver +to print debugging information about its decisions. +To force a particular resolver while also printing debugging information, +join the two settings by a plus sign, as in GODEBUG=netdns=go+1. + +On Plan 9, the resolver always accesses /net/cs and /net/dns. + +On Windows, the resolver always uses C library functions, such as GetAddrInfo and DnsQuery. + */ package net -// TODO(rsc): -// support for raw ethernet sockets - import ( "errors" "io" @@ -49,6 +86,20 @@ import ( "time" ) +// netGo and netCgo contain the state of the build tags used +// to build this binary, and whether cgo is available. +// conf.go mirrors these into conf for easier testing. +var ( + netGo bool // set true in cgo_stub.go for build tag "netgo" (or no cgo) + netCgo bool // set true in conf_netcgo.go for build tag "netcgo" +) + +func init() { + sysInit() + supportsIPv4 = probeIPv4Stack() + supportsIPv6, supportsIPv4map = probeIPv6Stack() +} + // Addr represents a network end point address. type Addr interface { Network() string // name of the network @@ -118,7 +169,11 @@ func (c *conn) Read(b []byte) (int, error) { if !c.ok() { return 0, syscall.EINVAL } - return c.fd.Read(b) + n, err := c.fd.Read(b) + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, err } // Write implements the Conn Write method. @@ -126,7 +181,11 @@ func (c *conn) Write(b []byte) (int, error) { if !c.ok() { return 0, syscall.EINVAL } - return c.fd.Write(b) + n, err := c.fd.Write(b) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, err } // Close closes the connection. @@ -134,10 +193,16 @@ func (c *conn) Close() error { if !c.ok() { return syscall.EINVAL } - return c.fd.Close() + err := c.fd.Close() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // LocalAddr returns the local network address. +// The Addr returned is shared by all invocations of LocalAddr, so +// do not modify it. func (c *conn) LocalAddr() Addr { if !c.ok() { return nil @@ -146,6 +211,8 @@ func (c *conn) LocalAddr() Addr { } // RemoteAddr returns the remote network address. +// The Addr returned is shared by all invocations of RemoteAddr, so +// do not modify it. func (c *conn) RemoteAddr() Addr { if !c.ok() { return nil @@ -158,7 +225,10 @@ func (c *conn) SetDeadline(t time.Time) error { if !c.ok() { return syscall.EINVAL } - return c.fd.setDeadline(t) + if err := c.fd.setDeadline(t); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil } // SetReadDeadline implements the Conn SetReadDeadline method. @@ -166,7 +236,10 @@ func (c *conn) SetReadDeadline(t time.Time) error { if !c.ok() { return syscall.EINVAL } - return c.fd.setReadDeadline(t) + if err := c.fd.setReadDeadline(t); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil } // SetWriteDeadline implements the Conn SetWriteDeadline method. @@ -174,7 +247,10 @@ func (c *conn) SetWriteDeadline(t time.Time) error { if !c.ok() { return syscall.EINVAL } - return c.fd.setWriteDeadline(t) + if err := c.fd.setWriteDeadline(t); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil } // SetReadBuffer sets the size of the operating system's @@ -183,7 +259,10 @@ func (c *conn) SetReadBuffer(bytes int) error { if !c.ok() { return syscall.EINVAL } - return setReadBuffer(c.fd, bytes) + if err := setReadBuffer(c.fd, bytes); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil } // SetWriteBuffer sets the size of the operating system's @@ -192,7 +271,10 @@ func (c *conn) SetWriteBuffer(bytes int) error { if !c.ok() { return syscall.EINVAL } - return setWriteBuffer(c.fd, bytes) + if err := setWriteBuffer(c.fd, bytes); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil } // File sets the underlying os.File to blocking mode and returns a copy. @@ -202,13 +284,12 @@ func (c *conn) SetWriteBuffer(bytes int) error { // The returned os.File's file descriptor is different from the connection's. // Attempting to change properties of the original using this duplicate // may or may not have the desired effect. -func (c *conn) File() (f *os.File, err error) { return c.fd.dup() } - -// An Error represents a network error. -type Error interface { - error - Timeout() bool // Is the error a timeout? - Temporary() bool // Is the error temporary? +func (c *conn) File() (f *os.File, err error) { + f, err = c.fd.dup() + if err != nil { + err = &OpError{Op: "file", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return } // PacketConn is a generic packet-oriented network connection. @@ -274,6 +355,13 @@ type Listener interface { Addr() Addr } +// An Error represents a network error. +type Error interface { + error + Timeout() bool // Is the error a timeout? + Temporary() bool // Is the error temporary? +} + // Various errors contained in OpError. var ( // For connection setup and write operations. @@ -281,6 +369,7 @@ var ( // For both read and write operations. errTimeout error = &timeoutError{} + errCanceled = errors.New("operation was canceled") errClosing = errors.New("use of closed network connection") ErrWriteToConnected = errors.New("use of WriteTo with pre-connected connection") ) @@ -297,7 +386,17 @@ type OpError struct { // such as "tcp" or "udp6". Net string - // Addr is the network address on which this error occurred. + // For operations involving a remote network connection, like + // Dial, Read, or Write, Source is the corresponding local + // network address. + Source Addr + + // Addr is the network address for which this error occurred. + // For local operations, like Listen or SetDeadline, Addr is + // the address of the local endpoint being manipulated. + // For operations involving a remote network connection, like + // Dial, Read, or Write, Addr is the remote address of that + // connection. Addr Addr // Err is the error that occurred during the operation. @@ -312,22 +411,21 @@ func (e *OpError) Error() string { if e.Net != "" { s += " " + e.Net } + if e.Source != nil { + s += " " + e.Source.String() + } if e.Addr != nil { - s += " " + e.Addr.String() + if e.Source != nil { + s += "->" + } else { + s += " " + } + s += e.Addr.String() } s += ": " + e.Err.Error() return s } -type temporary interface { - Temporary() bool -} - -func (e *OpError) Temporary() bool { - t, ok := e.Err.(temporary) - return ok && t.Temporary() -} - var noDeadline = time.Time{} type timeout interface { @@ -335,16 +433,45 @@ type timeout interface { } func (e *OpError) Timeout() bool { + if ne, ok := e.Err.(*os.SyscallError); ok { + t, ok := ne.Err.(timeout) + return ok && t.Timeout() + } t, ok := e.Err.(timeout) return ok && t.Timeout() } +type temporary interface { + Temporary() bool +} + +func (e *OpError) Temporary() bool { + if ne, ok := e.Err.(*os.SyscallError); ok { + t, ok := ne.Err.(temporary) + return ok && t.Temporary() + } + t, ok := e.Err.(temporary) + return ok && t.Temporary() +} + type timeoutError struct{} func (e *timeoutError) Error() string { return "i/o timeout" } func (e *timeoutError) Timeout() bool { return true } func (e *timeoutError) Temporary() bool { return true } +// A ParseError is the error type of literal network address parsers. +type ParseError struct { + // Type is the type of string that was expected, such as + // "IP address", "CIDR address". + Type string + + // Text is the malformed text string. + Text string +} + +func (e *ParseError) Error() string { return "invalid " + e.Type + ": " + e.Text } + type AddrError struct { Err string Addr string @@ -361,19 +488,14 @@ func (e *AddrError) Error() string { return s } -func (e *AddrError) Temporary() bool { - return false -} - -func (e *AddrError) Timeout() bool { - return false -} +func (e *AddrError) Timeout() bool { return false } +func (e *AddrError) Temporary() bool { return false } type UnknownNetworkError string func (e UnknownNetworkError) Error() string { return "unknown network " + string(e) } -func (e UnknownNetworkError) Temporary() bool { return false } func (e UnknownNetworkError) Timeout() bool { return false } +func (e UnknownNetworkError) Temporary() bool { return false } type InvalidAddrError string @@ -382,17 +504,50 @@ func (e InvalidAddrError) Timeout() bool { return false } func (e InvalidAddrError) Temporary() bool { return false } // DNSConfigError represents an error reading the machine's DNS configuration. +// (No longer used; kept for compatibility.) type DNSConfigError struct { Err error } -func (e *DNSConfigError) Error() string { - return "error reading DNS config: " + e.Err.Error() -} - +func (e *DNSConfigError) Error() string { return "error reading DNS config: " + e.Err.Error() } func (e *DNSConfigError) Timeout() bool { return false } func (e *DNSConfigError) Temporary() bool { return false } +// Various errors contained in DNSError. +var ( + errNoSuchHost = errors.New("no such host") +) + +// DNSError represents a DNS lookup error. +type DNSError struct { + Err string // description of the error + Name string // name looked for + Server string // server used + IsTimeout bool // if true, timed out; not all timeouts set this +} + +func (e *DNSError) Error() string { + if e == nil { + return "<nil>" + } + s := "lookup " + e.Name + if e.Server != "" { + s += " on " + e.Server + } + s += ": " + e.Err + return s +} + +// Timeout reports whether the DNS lookup is known to have timed out. +// This is not always known; a DNS lookup may fail due to a timeout +// and return a DNSError for which Timeout returns false. +func (e *DNSError) Timeout() bool { return e.IsTimeout } + +// Temporary reports whether the DNS error is known to be temporary. +// This is not always known; a DNS lookup may fail due to a temporary +// error and return a DNSError for which Temporary returns false. +func (e *DNSError) Temporary() bool { return e.IsTimeout } + type writerOnly struct { io.Writer } @@ -412,10 +567,6 @@ func genericReadFrom(w io.Writer, r io.Reader) (n int64, err error) { var threadLimit = make(chan struct{}, 500) -// Using send for acquire is fine here because we are not using this -// to protect any memory. All we care about is the number of goroutines -// making calls at a time. - func acquireThread() { threadLimit <- struct{}{} } diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go index bfed4d657fd..3907ce4aa56 100644 --- a/libgo/go/net/net_test.go +++ b/libgo/go/net/net_test.go @@ -6,258 +6,251 @@ package net import ( "io" - "io/ioutil" "os" "runtime" "testing" - "time" ) -func TestShutdown(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skipf("skipping test on %q", runtime.GOOS) +func TestCloseRead(t *testing.T) { + switch runtime.GOOS { + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) } - ln, err := Listen("tcp", "127.0.0.1:0") - if err != nil { - if ln, err = Listen("tcp6", "[::1]:0"); err != nil { - t.Fatalf("ListenTCP on :0: %v", err) + + for _, network := range []string{"tcp", "unix", "unixpacket"} { + if !testableNetwork(network) { + t.Logf("skipping %s test", network) + continue } - } - go func() { - defer ln.Close() - c, err := ln.Accept() + ln, err := newLocalListener(network) if err != nil { - t.Errorf("Accept: %v", err) - return + t.Fatal(err) } - var buf [10]byte - n, err := c.Read(buf[:]) - if n != 0 || err != io.EOF { - t.Errorf("server Read = %d, %v; want 0, io.EOF", n, err) - return + switch network { + case "unix", "unixpacket": + defer os.Remove(ln.Addr().String()) } - c.Write([]byte("response")) - c.Close() - }() + defer ln.Close() - c, err := Dial("tcp", ln.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer c.Close() + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + switch network { + case "unix", "unixpacket": + defer os.Remove(c.LocalAddr().String()) + } + defer c.Close() - err = c.(*TCPConn).CloseWrite() - if err != nil { - t.Fatalf("CloseWrite: %v", err) - } - var buf [10]byte - n, err := c.Read(buf[:]) - if err != nil { - t.Fatalf("client Read: %d, %v", n, err) - } - got := string(buf[:n]) - if got != "response" { - t.Errorf("read = %q, want \"response\"", got) + switch c := c.(type) { + case *TCPConn: + err = c.CloseRead() + case *UnixConn: + err = c.CloseRead() + } + if err != nil { + if perr := parseCloseError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + var b [1]byte + n, err := c.Read(b[:]) + if n != 0 || err == nil { + t.Fatalf("got (%d, %v); want (0, error)", n, err) + } } } -func TestShutdownUnix(t *testing.T) { +func TestCloseWrite(t *testing.T) { switch runtime.GOOS { - case "nacl", "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) } - f, err := ioutil.TempFile("", "go_net_unixtest") - if err != nil { - t.Fatalf("TempFile: %s", err) - } - f.Close() - tmpname := f.Name() - os.Remove(tmpname) - ln, err := Listen("unix", tmpname) - if err != nil { - t.Fatalf("ListenUnix on %s: %s", tmpname, err) - } - defer func() { - ln.Close() - os.Remove(tmpname) - }() - go func() { + handler := func(ls *localServer, ln Listener) { c, err := ln.Accept() if err != nil { - t.Errorf("Accept: %v", err) + t.Error(err) return } - var buf [10]byte - n, err := c.Read(buf[:]) + defer c.Close() + + var b [1]byte + n, err := c.Read(b[:]) if n != 0 || err != io.EOF { - t.Errorf("server Read = %d, %v; want 0, io.EOF", n, err) + t.Errorf("got (%d, %v); want (0, io.EOF)", n, err) + return + } + switch c := c.(type) { + case *TCPConn: + err = c.CloseWrite() + case *UnixConn: + err = c.CloseWrite() + } + if err != nil { + if perr := parseCloseError(err); perr != nil { + t.Error(perr) + } + t.Error(err) + return + } + n, err = c.Write(b[:]) + if err == nil { + t.Errorf("got (%d, %v); want (any, error)", n, err) return } - c.Write([]byte("response")) - c.Close() - }() - - c, err := Dial("unix", tmpname) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer c.Close() - - err = c.(*UnixConn).CloseWrite() - if err != nil { - t.Fatalf("CloseWrite: %v", err) - } - var buf [10]byte - n, err := c.Read(buf[:]) - if err != nil { - t.Fatalf("client Read: %d, %v", n, err) - } - got := string(buf[:n]) - if got != "response" { - t.Errorf("read = %q, want \"response\"", got) } -} -func TestTCPListenClose(t *testing.T) { - ln, err := Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen failed: %v", err) - } + for _, network := range []string{"tcp", "unix", "unixpacket"} { + if !testableNetwork(network) { + t.Logf("skipping %s test", network) + continue + } - done := make(chan bool, 1) - go func() { - time.Sleep(100 * time.Millisecond) - ln.Close() - }() - go func() { - c, err := ln.Accept() - if err == nil { - c.Close() - t.Error("Accept succeeded") - } else { - t.Logf("Accept timeout error: %s (any error is fine)", err) - } - done <- true - }() - select { - case <-done: - case <-time.After(2 * time.Second): - t.Fatal("timeout waiting for TCP close") - } -} + ls, err := newLocalServer(network) + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } -func TestUDPListenClose(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - ln, err := ListenPacket("udp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen failed: %v", err) - } + c, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + switch network { + case "unix", "unixpacket": + defer os.Remove(c.LocalAddr().String()) + } + defer c.Close() - buf := make([]byte, 1000) - done := make(chan bool, 1) - go func() { - time.Sleep(100 * time.Millisecond) - ln.Close() - }() - go func() { - _, _, err = ln.ReadFrom(buf) + switch c := c.(type) { + case *TCPConn: + err = c.CloseWrite() + case *UnixConn: + err = c.CloseWrite() + } + if err != nil { + if perr := parseCloseError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + var b [1]byte + n, err := c.Read(b[:]) + if n != 0 || err != io.EOF { + t.Fatalf("got (%d, %v); want (0, io.EOF)", n, err) + } + n, err = c.Write(b[:]) if err == nil { - t.Error("ReadFrom succeeded") - } else { - t.Logf("ReadFrom timeout error: %s (any error is fine)", err) - } - done <- true - }() - select { - case <-done: - case <-time.After(2 * time.Second): - t.Fatal("timeout waiting for UDP close") + t.Fatalf("got (%d, %v); want (any, error)", n, err) + } } } -func TestTCPClose(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - l, err := Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer l.Close() +func TestConnClose(t *testing.T) { + for _, network := range []string{"tcp", "unix", "unixpacket"} { + if !testableNetwork(network) { + t.Logf("skipping %s test", network) + continue + } - read := func(r io.Reader) error { - var m [1]byte - _, err := r.Read(m[:]) - return err - } + ln, err := newLocalListener(network) + if err != nil { + t.Fatal(err) + } + switch network { + case "unix", "unixpacket": + defer os.Remove(ln.Addr().String()) + } + defer ln.Close() - go func() { - c, err := Dial("tcp", l.Addr().String()) + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) if err != nil { - t.Errorf("Dial: %v", err) - return + t.Fatal(err) } + switch network { + case "unix", "unixpacket": + defer os.Remove(c.LocalAddr().String()) + } + defer c.Close() - go read(c) + if err := c.Close(); err != nil { + if perr := parseCloseError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + var b [1]byte + n, err := c.Read(b[:]) + if n != 0 || err == nil { + t.Fatalf("got (%d, %v); want (0, error)", n, err) + } + } +} - time.Sleep(10 * time.Millisecond) - c.Close() - }() +func TestListenerClose(t *testing.T) { + for _, network := range []string{"tcp", "unix", "unixpacket"} { + if !testableNetwork(network) { + t.Logf("skipping %s test", network) + continue + } - c, err := l.Accept() - if err != nil { - t.Fatal(err) - } - defer c.Close() + ln, err := newLocalListener(network) + if err != nil { + t.Fatal(err) + } + switch network { + case "unix", "unixpacket": + defer os.Remove(ln.Addr().String()) + } + defer ln.Close() - for err == nil { - err = read(c) - } - if err != nil && err != io.EOF { - t.Fatal(err) + if err := ln.Close(); err != nil { + if perr := parseCloseError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + c, err := ln.Accept() + if err == nil { + c.Close() + t.Fatal("should fail") + } } } -func TestErrorNil(t *testing.T) { - c, err := Dial("tcp", "127.0.0.1:65535") - if err == nil { - t.Fatal("Dial 127.0.0.1:65535 succeeded") - } - if c != nil { - t.Fatalf("Dial returned non-nil interface %T(%v) with err != nil", c, c) - } +func TestPacketConnClose(t *testing.T) { + for _, network := range []string{"udp", "unixgram"} { + if !testableNetwork(network) { + t.Logf("skipping %s test", network) + continue + } - // Make Listen fail by relistening on the same address. - l, err := Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen 127.0.0.1:0: %v", err) - } - defer l.Close() - l1, err := Listen("tcp", l.Addr().String()) - if err == nil { - t.Fatalf("second Listen %v: %v", l.Addr(), err) - } - if l1 != nil { - t.Fatalf("Listen returned non-nil interface %T(%v) with err != nil", l1, l1) - } + c, err := newLocalPacketListener(network) + if err != nil { + t.Fatal(err) + } + switch network { + case "unixgram": + defer os.Remove(c.LocalAddr().String()) + } + defer c.Close() - // Make ListenPacket fail by relistening on the same address. - lp, err := ListenPacket("udp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen 127.0.0.1:0: %v", err) - } - defer lp.Close() - lp1, err := ListenPacket("udp", lp.LocalAddr().String()) - if err == nil { - t.Fatalf("second Listen %v: %v", lp.LocalAddr(), err) - } - if lp1 != nil { - t.Fatalf("ListenPacket returned non-nil interface %T(%v) with err != nil", lp1, lp1) + if err := c.Close(); err != nil { + if perr := parseCloseError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + var b [1]byte + n, _, err := c.ReadFrom(b[:]) + if n != 0 || err == nil { + t.Fatalf("got (%d, %v); want (0, error)", n, err) + } } } diff --git a/libgo/go/net/non_unix_test.go b/libgo/go/net/non_unix_test.go new file mode 100644 index 00000000000..eddca562f98 --- /dev/null +++ b/libgo/go/net/non_unix_test.go @@ -0,0 +1,11 @@ +// Copyright 2015 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 nacl plan9 windows + +package net + +// See unix_test.go for what these (don't) do. +func forceGoDNS() func() { return func() {} } +func forceCgoDNS() bool { return false } diff --git a/libgo/go/net/nss.go b/libgo/go/net/nss.go new file mode 100644 index 00000000000..08c3e6a69fe --- /dev/null +++ b/libgo/go/net/nss.go @@ -0,0 +1,159 @@ +// Copyright 2015 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 dragonfly freebsd linux netbsd openbsd solaris + +package net + +import ( + "errors" + "io" + "os" +) + +// nssConf represents the state of the machine's /etc/nsswitch.conf file. +type nssConf struct { + err error // any error encountered opening or parsing the file + sources map[string][]nssSource // keyed by database (e.g. "hosts") +} + +type nssSource struct { + source string // e.g. "compat", "files", "mdns4_minimal" + criteria []nssCriterion +} + +// standardCriteria reports all specified criteria have the default +// status actions. +func (s nssSource) standardCriteria() bool { + for i, crit := range s.criteria { + if !crit.standardStatusAction(i == len(s.criteria)-1) { + return false + } + } + return true +} + +// nssCriterion is the parsed structure of one of the criteria in brackets +// after an NSS source name. +type nssCriterion struct { + negate bool // if "!" was present + status string // e.g. "success", "unavail" (lowercase) + action string // e.g. "return", "continue" (lowercase) +} + +// standardStatusAction reports whether c is equivalent to not +// specifying the criterion at all. last is whether this criteria is the +// last in the list. +func (c nssCriterion) standardStatusAction(last bool) bool { + if c.negate { + return false + } + var def string + switch c.status { + case "success": + def = "return" + case "notfound", "unavail", "tryagain": + def = "continue" + default: + // Unknown status + return false + } + if last && c.action == "return" { + return true + } + return c.action == def +} + +func parseNSSConfFile(file string) *nssConf { + f, err := os.Open(file) + if err != nil { + return &nssConf{err: err} + } + defer f.Close() + return parseNSSConf(f) +} + +func parseNSSConf(r io.Reader) *nssConf { + slurp, err := readFull(r) + if err != nil { + return &nssConf{err: err} + } + conf := new(nssConf) + conf.err = foreachLine(slurp, func(line []byte) error { + line = trimSpace(removeComment(line)) + if len(line) == 0 { + return nil + } + colon := bytesIndexByte(line, ':') + if colon == -1 { + return errors.New("no colon on line") + } + db := string(trimSpace(line[:colon])) + srcs := line[colon+1:] + for { + srcs = trimSpace(srcs) + if len(srcs) == 0 { + break + } + sp := bytesIndexByte(srcs, ' ') + var src string + if sp == -1 { + src = string(srcs) + srcs = nil // done + } else { + src = string(srcs[:sp]) + srcs = trimSpace(srcs[sp+1:]) + } + var criteria []nssCriterion + // See if there's a criteria block in brackets. + if len(srcs) > 0 && srcs[0] == '[' { + bclose := bytesIndexByte(srcs, ']') + if bclose == -1 { + return errors.New("unclosed criterion bracket") + } + var err error + criteria, err = parseCriteria(srcs[1:bclose]) + if err != nil { + return errors.New("invalid criteria: " + string(srcs[1:bclose])) + } + srcs = srcs[bclose+1:] + } + if conf.sources == nil { + conf.sources = make(map[string][]nssSource) + } + conf.sources[db] = append(conf.sources[db], nssSource{ + source: src, + criteria: criteria, + }) + } + return nil + }) + return conf +} + +// parses "foo=bar !foo=bar" +func parseCriteria(x []byte) (c []nssCriterion, err error) { + err = foreachField(x, func(f []byte) error { + not := false + if len(f) > 0 && f[0] == '!' { + not = true + f = f[1:] + } + if len(f) < 3 { + return errors.New("criterion too short") + } + eq := bytesIndexByte(f, '=') + if eq == -1 { + return errors.New("criterion lacks equal sign") + } + lowerASCIIBytes(f) + c = append(c, nssCriterion{ + negate: not, + status: string(f[:eq]), + action: string(f[eq+1:]), + }) + return nil + }) + return +} diff --git a/libgo/go/net/nss_test.go b/libgo/go/net/nss_test.go new file mode 100644 index 00000000000..371deb502d1 --- /dev/null +++ b/libgo/go/net/nss_test.go @@ -0,0 +1,169 @@ +// Copyright 2015 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 dragonfly freebsd linux netbsd openbsd solaris + +package net + +import ( + "reflect" + "strings" + "testing" +) + +const ubuntuTrustyAvahi = `# /etc/nsswitch.conf +# +# Example configuration of GNU Name Service Switch functionality. +# If you have the libc-doc-reference' and nfo' packages installed, try: +# nfo libc "Name Service Switch"' for information about this file. + +passwd: compat +group: compat +shadow: compat + +hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4 +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis +` + +func TestParseNSSConf(t *testing.T) { + tests := []struct { + name string + in string + want *nssConf + }{ + { + name: "no_newline", + in: "foo: a b", + want: &nssConf{ + sources: map[string][]nssSource{ + "foo": {{source: "a"}, {source: "b"}}, + }, + }, + }, + { + name: "newline", + in: "foo: a b\n", + want: &nssConf{ + sources: map[string][]nssSource{ + "foo": {{source: "a"}, {source: "b"}}, + }, + }, + }, + { + name: "whitespace", + in: " foo:a b \n", + want: &nssConf{ + sources: map[string][]nssSource{ + "foo": {{source: "a"}, {source: "b"}}, + }, + }, + }, + { + name: "comment1", + in: " foo:a b#c\n", + want: &nssConf{ + sources: map[string][]nssSource{ + "foo": {{source: "a"}, {source: "b"}}, + }, + }, + }, + { + name: "comment2", + in: " foo:a b #c \n", + want: &nssConf{ + sources: map[string][]nssSource{ + "foo": {{source: "a"}, {source: "b"}}, + }, + }, + }, + { + name: "crit", + in: " foo:a b [!a=b X=Y ] c#d \n", + want: &nssConf{ + sources: map[string][]nssSource{ + "foo": { + {source: "a"}, + { + source: "b", + criteria: []nssCriterion{ + { + negate: true, + status: "a", + action: "b", + }, + { + status: "x", + action: "y", + }, + }, + }, + {source: "c"}, + }, + }, + }, + }, + + // Ubuntu Trusty w/ avahi-daemon, libavahi-* etc installed. + { + name: "ubuntu_trusty_avahi", + in: ubuntuTrustyAvahi, + want: &nssConf{ + sources: map[string][]nssSource{ + "passwd": {{source: "compat"}}, + "group": {{source: "compat"}}, + "shadow": {{source: "compat"}}, + "hosts": { + {source: "files"}, + { + source: "mdns4_minimal", + criteria: []nssCriterion{ + { + negate: false, + status: "notfound", + action: "return", + }, + }, + }, + {source: "dns"}, + {source: "mdns4"}, + }, + "networks": {{source: "files"}}, + "protocols": { + {source: "db"}, + {source: "files"}, + }, + "services": { + {source: "db"}, + {source: "files"}, + }, + "ethers": { + {source: "db"}, + {source: "files"}, + }, + "rpc": { + {source: "db"}, + {source: "files"}, + }, + "netgroup": { + {source: "nis"}, + }, + }, + }, + }, + } + + for _, tt := range tests { + gotConf := parseNSSConf(strings.NewReader(tt.in)) + if !reflect.DeepEqual(gotConf, tt.want) { + t.Errorf("%s: mismatch\n got %#v\nwant %#v", tt.name, gotConf, tt.want) + } + } +} diff --git a/libgo/go/net/packetconn_test.go b/libgo/go/net/packetconn_test.go index b6e4e76f930..7f3ea8a2d0c 100644 --- a/libgo/go/net/packetconn_test.go +++ b/libgo/go/net/packetconn_test.go @@ -9,49 +9,21 @@ package net import ( "os" - "runtime" - "strings" "testing" "time" ) -func packetConnTestData(t *testing.T, net string, i int) ([]byte, func()) { - switch net { - case "udp": - return []byte("UDP PACKETCONN TEST"), nil - case "ip": - if skip, skipmsg := skipRawSocketTest(t); skip { - return nil, func() { - t.Logf(skipmsg) - } - } - b, err := (&icmpMessage{ - Type: icmpv4EchoRequest, Code: 0, - Body: &icmpEcho{ - ID: os.Getpid() & 0xffff, Seq: i + 1, - Data: []byte("IP PACKETCONN TEST"), - }, - }).Marshal() - if err != nil { - return nil, func() { - t.Fatalf("icmpMessage.Marshal failed: %v", err) - } - } - return b, nil - case "unixgram": - switch runtime.GOOS { - case "nacl", "plan9", "windows": - return nil, func() { - t.Logf("skipping %q test on %q", net, runtime.GOOS) - } - default: - return []byte("UNIXGRAM PACKETCONN TEST"), nil - } - default: - return nil, func() { - t.Logf("skipping %q test", net) - } +// The full stack test cases for IPConn have been moved to the +// following: +// golang.org/x/net/ipv4 +// golang.org/x/net/ipv6 +// golang.org/x/net/icmp + +func packetConnTestData(t *testing.T, network string) ([]byte, func()) { + if !testableNetwork(network) { + return nil, func() { t.Logf("skipping %s test", network) } } + return []byte("PACKETCONN TEST"), nil } var packetConnTests = []struct { @@ -60,7 +32,6 @@ var packetConnTests = []struct { addr2 string }{ {"udp", "127.0.0.1:0", "127.0.0.1:0"}, - {"ip:icmp", "127.0.0.1", "127.0.0.1"}, {"unixgram", testUnixAddr(), testUnixAddr()}, } @@ -74,9 +45,8 @@ func TestPacketConn(t *testing.T) { } } - for i, tt := range packetConnTests { - netstr := strings.Split(tt.net, ":") - wb, skipOrFatalFn := packetConnTestData(t, netstr[0], i) + for _, tt := range packetConnTests { + wb, skipOrFatalFn := packetConnTestData(t, tt.net) if skipOrFatalFn != nil { skipOrFatalFn() continue @@ -84,37 +54,37 @@ func TestPacketConn(t *testing.T) { c1, err := ListenPacket(tt.net, tt.addr1) if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } - defer closer(c1, netstr[0], tt.addr1, tt.addr2) + defer closer(c1, tt.net, tt.addr1, tt.addr2) 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)) + c1.SetDeadline(time.Now().Add(500 * time.Millisecond)) + c1.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + c1.SetWriteDeadline(time.Now().Add(500 * time.Millisecond)) c2, err := ListenPacket(tt.net, tt.addr2) if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } - defer closer(c2, netstr[0], tt.addr1, tt.addr2) + defer closer(c2, tt.net, tt.addr1, tt.addr2) 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)) + c2.SetDeadline(time.Now().Add(500 * time.Millisecond)) + c2.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + c2.SetWriteDeadline(time.Now().Add(500 * time.Millisecond)) + rb2 := make([]byte, 128) if _, err := c1.WriteTo(wb, c2.LocalAddr()); err != nil { - t.Fatalf("PacketConn.WriteTo failed: %v", err) + t.Fatal(err) } - rb2 := make([]byte, 128) if _, _, err := c2.ReadFrom(rb2); err != nil { - t.Fatalf("PacketConn.ReadFrom failed: %v", err) + t.Fatal(err) } if _, err := c2.WriteTo(wb, c1.LocalAddr()); err != nil { - t.Fatalf("PacketConn.WriteTo failed: %v", err) + t.Fatal(err) } rb1 := make([]byte, 128) if _, _, err := c1.ReadFrom(rb1); err != nil { - t.Fatalf("PacketConn.ReadFrom failed: %v", err) + t.Fatal(err) } } } @@ -129,10 +99,9 @@ func TestConnAndPacketConn(t *testing.T) { } } - for i, tt := range packetConnTests { + for _, tt := range packetConnTests { var wb []byte - netstr := strings.Split(tt.net, ":") - wb, skipOrFatalFn := packetConnTestData(t, netstr[0], i) + wb, skipOrFatalFn := packetConnTestData(t, tt.net) if skipOrFatalFn != nil { skipOrFatalFn() continue @@ -140,47 +109,45 @@ func TestConnAndPacketConn(t *testing.T) { c1, err := ListenPacket(tt.net, tt.addr1) if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } - defer closer(c1, netstr[0], tt.addr1, tt.addr2) + defer closer(c1, tt.net, tt.addr1, tt.addr2) 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)) + c1.SetDeadline(time.Now().Add(500 * time.Millisecond)) + c1.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + c1.SetWriteDeadline(time.Now().Add(500 * time.Millisecond)) c2, err := Dial(tt.net, c1.LocalAddr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c2.Close() 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.SetDeadline(time.Now().Add(500 * time.Millisecond)) + c2.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + c2.SetWriteDeadline(time.Now().Add(500 * time.Millisecond)) if _, err := c2.Write(wb); err != nil { - t.Fatalf("Conn.Write failed: %v", err) + t.Fatal(err) } rb1 := make([]byte, 128) if _, _, err := c1.ReadFrom(rb1); err != nil { - t.Fatalf("PacketConn.ReadFrom failed: %v", err) + t.Fatal(err) } var dst Addr - switch netstr[0] { - case "ip": - dst = &IPAddr{IP: IPv4(127, 0, 0, 1)} + switch tt.net { case "unixgram": continue default: dst = c2.LocalAddr() } if _, err := c1.WriteTo(wb, dst); err != nil { - t.Fatalf("PacketConn.WriteTo failed: %v", err) + t.Fatal(err) } rb2 := make([]byte, 128) if _, err := c2.Read(rb2); err != nil { - t.Fatalf("Conn.Read failed: %v", err) + t.Fatal(err) } } } diff --git a/libgo/go/net/parse.go b/libgo/go/net/parse.go index e1d0130c9ac..c72e1c2eaf0 100644 --- a/libgo/go/net/parse.go +++ b/libgo/go/net/parse.go @@ -171,43 +171,30 @@ func xtoi2(s string, e byte) (byte, bool) { return byte(n), ok && ei == 2 } -// Integer to decimal. -func itoa(i int) string { - var buf [30]byte - n := len(buf) - neg := false - if i < 0 { - i = -i - neg = true - } - ui := uint(i) - for ui > 0 || n == len(buf) { - n-- - buf[n] = byte('0' + ui%10) - ui /= 10 - } - if neg { - n-- - buf[n] = '-' - } - return string(buf[n:]) -} - -// Convert i to decimal string. -func itod(i uint) string { - if i == 0 { - return "0" +// Convert integer to decimal string. +func itoa(val int) string { + if val < 0 { + return "-" + uitoa(uint(-val)) } + return uitoa(uint(val)) +} - // Assemble decimal in reverse order. - var b [32]byte - bp := len(b) - for ; i > 0; i /= 10 { - bp-- - b[bp] = byte(i%10) + '0' +// Convert unsigned integer to decimal string. +func uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" } - - return string(b[bp:]) + var buf [20]byte // big enough for 64bit value base 10 + i := len(buf) - 1 + for val >= 10 { + q := val / 10 + buf[i] = byte('0' + val - q*10) + i-- + val = q + } + // val < 10 + buf[i] = byte('0' + val) + return string(buf[i:]) } // Convert i to a hexadecimal string. Leading zeros are not printed. @@ -245,3 +232,155 @@ func last(s string, b byte) int { } return i } + +// lowerASCIIBytes makes x ASCII lowercase in-place. +func lowerASCIIBytes(x []byte) { + for i, b := range x { + if 'A' <= b && b <= 'Z' { + x[i] += 'a' - 'A' + } + } +} + +// lowerASCII returns the ASCII lowercase version of b. +func lowerASCII(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// trimSpace returns x without any leading or trailing ASCII whitespace. +func trimSpace(x []byte) []byte { + for len(x) > 0 && isSpace(x[0]) { + x = x[1:] + } + for len(x) > 0 && isSpace(x[len(x)-1]) { + x = x[:len(x)-1] + } + return x +} + +// isSpace reports whether b is an ASCII space character. +func isSpace(b byte) bool { + return b == ' ' || b == '\t' || b == '\n' || b == '\r' +} + +// removeComment returns line, removing any '#' byte and any following +// bytes. +func removeComment(line []byte) []byte { + if i := bytesIndexByte(line, '#'); i != -1 { + return line[:i] + } + return line +} + +// foreachLine runs fn on each line of x. +// Each line (except for possibly the last) ends in '\n'. +// It returns the first non-nil error returned by fn. +func foreachLine(x []byte, fn func(line []byte) error) error { + for len(x) > 0 { + nl := bytesIndexByte(x, '\n') + if nl == -1 { + return fn(x) + } + line := x[:nl+1] + x = x[nl+1:] + if err := fn(line); err != nil { + return err + } + } + return nil +} + +// foreachField runs fn on each non-empty run of non-space bytes in x. +// It returns the first non-nil error returned by fn. +func foreachField(x []byte, fn func(field []byte) error) error { + x = trimSpace(x) + for len(x) > 0 { + sp := bytesIndexByte(x, ' ') + if sp == -1 { + return fn(x) + } + if field := trimSpace(x[:sp]); len(field) > 0 { + if err := fn(field); err != nil { + return err + } + } + x = trimSpace(x[sp+1:]) + } + return nil +} + +// bytesIndexByte is bytes.IndexByte. It returns the index of the +// first instance of c in s, or -1 if c is not present in s. +func bytesIndexByte(s []byte, c byte) int { + for i, b := range s { + if b == c { + return i + } + } + return -1 +} + +// stringsHasSuffix is strings.HasSuffix. It reports whether s ends in +// suffix. +func stringsHasSuffix(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// stringsHasSuffixFold reports whether s ends in suffix, +// ASCII-case-insensitively. +func stringsHasSuffixFold(s, suffix string) bool { + if len(suffix) > len(s) { + return false + } + for i := 0; i < len(suffix); i++ { + if lowerASCII(suffix[i]) != lowerASCII(s[len(s)-len(suffix)+i]) { + return false + } + } + return true +} + +// stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix. +func stringsHasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func readFull(r io.Reader) (all []byte, err error) { + buf := make([]byte, 1024) + for { + n, err := r.Read(buf) + all = append(all, buf[:n]...) + if err == io.EOF { + return all, nil + } + if err != nil { + return nil, err + } + } +} + +// goDebugString returns the value of the named GODEBUG key. +// GODEBUG is of the form "key=val,key2=val2" +func goDebugString(key string) string { + s := os.Getenv("GODEBUG") + for i := 0; i < len(s)-len(key)-1; i++ { + if i > 0 && s[i-1] != ',' { + continue + } + afterKey := s[i+len(key):] + if afterKey[0] != '=' || s[i:i+len(key)] != key { + continue + } + val := afterKey[1:] + for i, b := range val { + if b == ',' { + return val[:i] + } + } + return val + } + return "" +} diff --git a/libgo/go/net/parse_test.go b/libgo/go/net/parse_test.go index 7b213b75bde..0f048fcea0b 100644 --- a/libgo/go/net/parse_test.go +++ b/libgo/go/net/parse_test.go @@ -15,20 +15,20 @@ func TestReadLine(t *testing.T) { // /etc/services file does not exist on android, plan9, windows. switch runtime.GOOS { case "android", "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } filename := "/etc/services" // a nice big file fd, err := os.Open(filename) if err != nil { - t.Fatalf("open %s: %v", filename, err) + t.Fatal(err) } defer fd.Close() br := bufio.NewReader(fd) file, err := open(filename) if file == nil { - t.Fatalf("net.open(%s) = nil", filename) + t.Fatal(err) } defer file.close() @@ -41,8 +41,7 @@ func TestReadLine(t *testing.T) { } line, ok := file.readLine() if (berr != nil) != !ok || bline != line { - t.Fatalf("%s:%d (#%d)\nbufio => %q, %v\nnet => %q, %v", - filename, lineno, byteno, bline, berr, line, ok) + t.Fatalf("%s:%d (#%d)\nbufio => %q, %v\nnet => %q, %v", filename, lineno, byteno, bline, berr, line, ok) } if !ok { break @@ -51,3 +50,30 @@ func TestReadLine(t *testing.T) { byteno += len(line) + 1 } } + +func TestGoDebugString(t *testing.T) { + defer os.Setenv("GODEBUG", os.Getenv("GODEBUG")) + tests := []struct { + godebug string + key string + want string + }{ + {"", "foo", ""}, + {"foo=", "foo", ""}, + {"foo=bar", "foo", "bar"}, + {"foo=bar,", "foo", "bar"}, + {"foo,foo=bar,", "foo", "bar"}, + {"foo1=bar,foo=bar,", "foo", "bar"}, + {"foo=bar,foo=bar,", "foo", "bar"}, + {"foo=", "foo", ""}, + {"foo", "foo", ""}, + {",foo", "foo", ""}, + {"foo=bar,baz", "loooooooong", ""}, + } + for _, tt := range tests { + os.Setenv("GODEBUG", tt.godebug) + if got := goDebugString(tt.key); got != tt.want { + t.Errorf("for %q, goDebugString(%q) = %q; want %q", tt.godebug, tt.key, got, tt.want) + } + } +} diff --git a/libgo/go/net/pipe.go b/libgo/go/net/pipe.go index f1a2eca4e88..5fc830b7408 100644 --- a/libgo/go/net/pipe.go +++ b/libgo/go/net/pipe.go @@ -55,13 +55,13 @@ func (p *pipe) RemoteAddr() Addr { } func (p *pipe) SetDeadline(t time.Time) error { - return errors.New("net.Pipe does not support deadlines") + return &OpError{Op: "set", Net: "pipe", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} } func (p *pipe) SetReadDeadline(t time.Time) error { - return errors.New("net.Pipe does not support deadlines") + return &OpError{Op: "set", Net: "pipe", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} } func (p *pipe) SetWriteDeadline(t time.Time) error { - return errors.New("net.Pipe does not support deadlines") + return &OpError{Op: "set", Net: "pipe", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} } diff --git a/libgo/go/net/pipe_test.go b/libgo/go/net/pipe_test.go index afe4f2408fa..60c39205932 100644 --- a/libgo/go/net/pipe_test.go +++ b/libgo/go/net/pipe_test.go @@ -10,10 +10,10 @@ import ( "testing" ) -func checkWrite(t *testing.T, w io.Writer, data []byte, c chan int) { +func checkPipeWrite(t *testing.T, w io.Writer, data []byte, c chan int) { n, err := w.Write(data) if err != nil { - t.Errorf("write: %v", err) + t.Error(err) } if n != len(data) { t.Errorf("short write: %d != %d", n, len(data)) @@ -21,11 +21,11 @@ func checkWrite(t *testing.T, w io.Writer, data []byte, c chan int) { c <- 0 } -func checkRead(t *testing.T, r io.Reader, data []byte, wantErr error) { +func checkPipeRead(t *testing.T, r io.Reader, data []byte, wantErr error) { buf := make([]byte, len(data)+10) n, err := r.Read(buf) if err != wantErr { - t.Errorf("read: %v", err) + t.Error(err) return } if n != len(data) || !bytes.Equal(buf[0:n], data) { @@ -34,23 +34,22 @@ func checkRead(t *testing.T, r io.Reader, data []byte, wantErr error) { } } -// Test a simple read/write/close sequence. +// TestPipe tests a simple read/write/close sequence. // Assumes that the underlying io.Pipe implementation // is solid and we're just testing the net wrapping. - func TestPipe(t *testing.T) { c := make(chan int) cli, srv := Pipe() - go checkWrite(t, cli, []byte("hello, world"), c) - checkRead(t, srv, []byte("hello, world"), nil) + go checkPipeWrite(t, cli, []byte("hello, world"), c) + checkPipeRead(t, srv, []byte("hello, world"), nil) <-c - go checkWrite(t, srv, []byte("line 2"), c) - checkRead(t, cli, []byte("line 2"), nil) + go checkPipeWrite(t, srv, []byte("line 2"), c) + checkPipeRead(t, cli, []byte("line 2"), nil) <-c - go checkWrite(t, cli, []byte("a third line"), c) - checkRead(t, srv, []byte("a third line"), nil) + go checkPipeWrite(t, cli, []byte("a third line"), c) + checkPipeRead(t, srv, []byte("a third line"), nil) <-c go srv.Close() - checkRead(t, cli, nil, io.EOF) + checkPipeRead(t, cli, nil, io.EOF) cli.Close() } diff --git a/libgo/go/net/platform_test.go b/libgo/go/net/platform_test.go new file mode 100644 index 00000000000..d6248520f33 --- /dev/null +++ b/libgo/go/net/platform_test.go @@ -0,0 +1,159 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package net + +import ( + "os" + "runtime" + "strings" + "testing" +) + +// testableNetwork reports whether network is testable on the current +// platform configuration. +func testableNetwork(network string) bool { + ss := strings.Split(network, ":") + switch ss[0] { + case "ip+nopriv": + switch runtime.GOOS { + case "nacl": + return false + } + case "ip", "ip4", "ip6": + switch runtime.GOOS { + case "nacl", "plan9": + return false + default: + if os.Getuid() != 0 { + return false + } + } + case "unix", "unixgram": + switch runtime.GOOS { + case "nacl", "plan9", "windows": + return false + } + // iOS does not support unix, unixgram. + if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { + return false + } + case "unixpacket": + switch runtime.GOOS { + case "android", "darwin", "nacl", "plan9", "windows": + fallthrough + case "freebsd": // FreeBSD 8 and below don't support unixpacket + return false + } + } + switch ss[0] { + case "tcp4", "udp4", "ip4": + if !supportsIPv4 { + return false + } + case "tcp6", "udp6", "ip6": + if !supportsIPv6 { + return false + } + } + return true +} + +// testableAddress reports whether address of network is testable on +// the current platform configuration. +func testableAddress(network, address string) bool { + switch ss := strings.Split(network, ":"); ss[0] { + case "unix", "unixgram", "unixpacket": + // Abstract unix domain sockets, a Linux-ism. + if address[0] == '@' && runtime.GOOS != "linux" { + return false + } + } + return true +} + +// testableListenArgs reports whether arguments are testable on the +// current platform configuration. +func testableListenArgs(network, address, client string) bool { + if !testableNetwork(network) || !testableAddress(network, address) { + return false + } + + var err error + var addr Addr + switch ss := strings.Split(network, ":"); ss[0] { + case "tcp", "tcp4", "tcp6": + addr, err = ResolveTCPAddr("tcp", address) + case "udp", "udp4", "udp6": + addr, err = ResolveUDPAddr("udp", address) + case "ip", "ip4", "ip6": + addr, err = ResolveIPAddr("ip", address) + default: + return true + } + if err != nil { + return false + } + var ip IP + var wildcard bool + switch addr := addr.(type) { + case *TCPAddr: + ip = addr.IP + wildcard = addr.isWildcard() + case *UDPAddr: + ip = addr.IP + wildcard = addr.isWildcard() + case *IPAddr: + ip = addr.IP + wildcard = addr.isWildcard() + } + + // Test wildcard IP addresses. + if wildcard && (testing.Short() || !*testExternal) { + return false + } + + // Test functionality of IPv4 communication using AF_INET and + // IPv6 communication using AF_INET6 sockets. + if !supportsIPv4 && ip.To4() != nil { + return false + } + if !supportsIPv6 && ip.To16() != nil && ip.To4() == nil { + return false + } + cip := ParseIP(client) + if cip != nil { + if !supportsIPv4 && cip.To4() != nil { + return false + } + if !supportsIPv6 && cip.To16() != nil && cip.To4() == nil { + return false + } + } + + // Test functionality of IPv4 communication using AF_INET6 + // sockets. + if !supportsIPv4map && (network == "tcp" || network == "udp" || network == "ip") && wildcard { + // At this point, we prefer IPv4 when ip is nil. + // See favoriteAddrFamily for further information. + if ip.To16() != nil && ip.To4() == nil && cip.To4() != nil { // a pair of IPv6 server and IPv4 client + return false + } + if (ip.To4() != nil || ip == nil) && cip.To16() != nil && cip.To4() == nil { // a pair of IPv4 server and IPv6 client + return false + } + } + + return true +} + +var condFatalf = func() func(*testing.T, string, ...interface{}) { + // A few APIs, File, Read/WriteMsg{UDP,IP}, are not + // implemented yet on both Plan 9 and Windows. + switch runtime.GOOS { + case "plan9", "windows": + return (*testing.T).Logf + } + return (*testing.T).Fatalf +}() diff --git a/libgo/go/net/port.go b/libgo/go/net/port.go index c24f4ed5b17..a2a538789e1 100644 --- a/libgo/go/net/port.go +++ b/libgo/go/net/port.go @@ -18,7 +18,7 @@ func parsePort(net, port string) (int, error) { } } if p < 0 || p > 0xFFFF { - return 0, &AddrError{"invalid port", port} + return 0, &AddrError{Err: "invalid port", Addr: port} } return p, nil } diff --git a/libgo/go/net/port_test.go b/libgo/go/net/port_test.go index 4811ade69e0..2dacd975e7a 100644 --- a/libgo/go/net/port_test.go +++ b/libgo/go/net/port_test.go @@ -9,14 +9,12 @@ import ( "testing" ) -type portTest struct { - netw string - name string - port int - ok bool -} - -var porttests = []portTest{ +var portTests = []struct { + network string + name string + port int + ok bool +}{ {"tcp", "echo", 7, true}, {"tcp", "discard", 9, true}, {"tcp", "systat", 11, true}, @@ -46,14 +44,12 @@ var porttests = []portTest{ func TestLookupPort(t *testing.T) { switch runtime.GOOS { case "nacl": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - for i := 0; i < len(porttests); i++ { - tt := porttests[i] - if port, err := LookupPort(tt.netw, tt.name); port != tt.port || (err == nil) != tt.ok { - t.Errorf("LookupPort(%q, %q) = %v, %v; want %v", - tt.netw, tt.name, port, err, tt.port) + for _, tt := range portTests { + if port, err := LookupPort(tt.network, tt.name); port != tt.port || (err == nil) != tt.ok { + t.Errorf("LookupPort(%q, %q) = %v, %v; want %v", tt.network, tt.name, port, err, tt.port) } } } diff --git a/libgo/go/net/port_unix.go b/libgo/go/net/port_unix.go index 348c771c351..badf8abc79b 100644 --- a/libgo/go/net/port_unix.go +++ b/libgo/go/net/port_unix.go @@ -69,5 +69,5 @@ func goLookupPort(network, service string) (port int, err error) { return } } - return 0, &AddrError{"unknown port", network + "/" + service} + return 0, &AddrError{Err: "unknown port", Addr: network + "/" + service} } diff --git a/libgo/go/net/protoconn_test.go b/libgo/go/net/protoconn_test.go index 12856b6c311..c6ef23b0e18 100644 --- a/libgo/go/net/protoconn_test.go +++ b/libgo/go/net/protoconn_test.go @@ -8,49 +8,31 @@ package net import ( - "io/ioutil" "os" "runtime" "testing" "time" ) -// testUnixAddr uses ioutil.TempFile to get a name that is unique. It -// also uses /tmp directory in case it is prohibited to create UNIX -// sockets in TMPDIR. -func testUnixAddr() string { - f, err := ioutil.TempFile("", "nettest") - if err != nil { - panic(err) - } - addr := f.Name() - f.Close() - os.Remove(addr) - return addr -} - -var condFatalf = 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).Fatalf -}() +// The full stack test cases for IPConn have been moved to the +// following: +// golang.org/x/net/ipv4 +// golang.org/x/net/ipv6 +// golang.org/x/net/icmp func TestTCPListenerSpecificMethods(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } la, err := ResolveTCPAddr("tcp4", "127.0.0.1:0") if err != nil { - t.Fatalf("ResolveTCPAddr failed: %v", err) + t.Fatal(err) } ln, err := ListenTCP("tcp4", la) if err != nil { - t.Fatalf("ListenTCP failed: %v", err) + t.Fatal(err) } defer ln.Close() ln.Addr() @@ -58,21 +40,21 @@ func TestTCPListenerSpecificMethods(t *testing.T) { if c, err := ln.Accept(); err != nil { if !err.(Error).Timeout() { - t.Fatalf("TCPListener.Accept failed: %v", err) + t.Fatal(err) } } else { c.Close() } if c, err := ln.AcceptTCP(); err != nil { if !err.(Error).Timeout() { - t.Fatalf("TCPListener.AcceptTCP failed: %v", err) + t.Fatal(err) } } else { c.Close() } if f, err := ln.File(); err != nil { - condFatalf(t, "TCPListener.File failed: %v", err) + condFatalf(t, "%v", err) } else { f.Close() } @@ -81,25 +63,30 @@ func TestTCPListenerSpecificMethods(t *testing.T) { func TestTCPConnSpecificMethods(t *testing.T) { la, err := ResolveTCPAddr("tcp4", "127.0.0.1:0") if err != nil { - t.Fatalf("ResolveTCPAddr failed: %v", err) + t.Fatal(err) } ln, err := ListenTCP("tcp4", la) if err != nil { - t.Fatalf("ListenTCP failed: %v", err) + t.Fatal(err) + } + ch := make(chan error, 1) + handler := func(ls *localServer, ln Listener) { transponder(ls.Listener, ch) } + ls, err := (&streamListener{Listener: ln}).newLocalServer() + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) } - defer ln.Close() - ln.Addr() - - done := make(chan int) - go transponder(t, ln, done) - ra, err := ResolveTCPAddr("tcp4", ln.Addr().String()) + ra, err := ResolveTCPAddr("tcp4", ls.Listener.Addr().String()) if err != nil { - t.Fatalf("ResolveTCPAddr failed: %v", err) + t.Fatal(err) } c, err := DialTCP("tcp4", nil, ra) if err != nil { - t.Fatalf("DialTCP failed: %v", err) + t.Fatal(err) } defer c.Close() c.SetKeepAlive(false) @@ -113,24 +100,26 @@ func TestTCPConnSpecificMethods(t *testing.T) { c.SetWriteDeadline(time.Now().Add(someTimeout)) if _, err := c.Write([]byte("TCPCONN TEST")); err != nil { - t.Fatalf("TCPConn.Write failed: %v", err) + t.Fatal(err) } rb := make([]byte, 128) if _, err := c.Read(rb); err != nil { - t.Fatalf("TCPConn.Read failed: %v", err) + t.Fatal(err) } - <-done + for err := range ch { + t.Error(err) + } } func TestUDPConnSpecificMethods(t *testing.T) { la, err := ResolveUDPAddr("udp4", "127.0.0.1:0") if err != nil { - t.Fatalf("ResolveUDPAddr failed: %v", err) + t.Fatal(err) } c, err := ListenUDP("udp4", la) if err != nil { - t.Fatalf("ListenUDP failed: %v", err) + t.Fatal(err) } defer c.Close() c.LocalAddr() @@ -144,27 +133,27 @@ func TestUDPConnSpecificMethods(t *testing.T) { wb := []byte("UDPCONN TEST") rb := make([]byte, 128) if _, err := c.WriteToUDP(wb, c.LocalAddr().(*UDPAddr)); err != nil { - t.Fatalf("UDPConn.WriteToUDP failed: %v", err) + t.Fatal(err) } if _, _, err := c.ReadFromUDP(rb); err != nil { - t.Fatalf("UDPConn.ReadFromUDP failed: %v", err) + t.Fatal(err) } if _, _, err := c.WriteMsgUDP(wb, nil, c.LocalAddr().(*UDPAddr)); err != nil { - condFatalf(t, "UDPConn.WriteMsgUDP failed: %v", err) + condFatalf(t, "%v", err) } if _, _, _, _, err := c.ReadMsgUDP(rb, nil); err != nil { - condFatalf(t, "UDPConn.ReadMsgUDP failed: %v", err) + condFatalf(t, "%v", err) } if f, err := c.File(); err != nil { - condFatalf(t, "UDPConn.File failed: %v", err) + condFatalf(t, "%v", err) } else { f.Close() } defer func() { if p := recover(); p != nil { - t.Fatalf("UDPConn.WriteToUDP or WriteMsgUDP panicked: %v", p) + t.Fatalf("panicked: %v", p) } }() @@ -173,17 +162,17 @@ func TestUDPConnSpecificMethods(t *testing.T) { } func TestIPConnSpecificMethods(t *testing.T) { - if skip, skipmsg := skipRawSocketTest(t); skip { - t.Skip(skipmsg) + if os.Getuid() != 0 { + t.Skip("must be root") } la, err := ResolveIPAddr("ip4", "127.0.0.1") if err != nil { - t.Fatalf("ResolveIPAddr failed: %v", err) + t.Fatal(err) } c, err := ListenIP("ip4:icmp", la) if err != nil { - t.Fatalf("ListenIP failed: %v", err) + t.Fatal(err) } defer c.Close() c.LocalAddr() @@ -194,60 +183,36 @@ func TestIPConnSpecificMethods(t *testing.T) { c.SetReadBuffer(2048) c.SetWriteBuffer(2048) - wb, err := (&icmpMessage{ - Type: icmpv4EchoRequest, Code: 0, - Body: &icmpEcho{ - ID: os.Getpid() & 0xffff, Seq: 1, - Data: []byte("IPCONN TEST "), - }, - }).Marshal() - if err != nil { - t.Fatalf("icmpMessage.Marshal failed: %v", err) - } - rb := make([]byte, 20+len(wb)) - if _, err := c.WriteToIP(wb, c.LocalAddr().(*IPAddr)); err != nil { - t.Fatalf("IPConn.WriteToIP failed: %v", err) - } - if _, _, err := c.ReadFromIP(rb); err != nil { - t.Fatalf("IPConn.ReadFromIP failed: %v", err) - } - if _, _, err := c.WriteMsgIP(wb, nil, c.LocalAddr().(*IPAddr)); err != nil { - condFatalf(t, "IPConn.WriteMsgIP failed: %v", err) - } - if _, _, _, _, err := c.ReadMsgIP(rb, nil); err != nil { - condFatalf(t, "IPConn.ReadMsgIP failed: %v", err) - } - if f, err := c.File(); err != nil { - condFatalf(t, "IPConn.File failed: %v", err) + condFatalf(t, "%v", err) } else { f.Close() } defer func() { if p := recover(); p != nil { - t.Fatalf("IPConn.WriteToIP or WriteMsgIP panicked: %v", p) + t.Fatalf("panicked: %v", p) } }() + wb := []byte("IPCONN TEST") c.WriteToIP(wb, nil) c.WriteMsgIP(wb, nil, nil) } func TestUnixListenerSpecificMethods(t *testing.T) { - switch runtime.GOOS { - case "nacl", "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) + if !testableNetwork("unix") { + t.Skip("unix test") } addr := testUnixAddr() la, err := ResolveUnixAddr("unix", addr) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } ln, err := ListenUnix("unix", la) if err != nil { - t.Fatalf("ListenUnix failed: %v", err) + t.Fatal(err) } defer ln.Close() defer os.Remove(addr) @@ -256,41 +221,40 @@ func TestUnixListenerSpecificMethods(t *testing.T) { if c, err := ln.Accept(); err != nil { if !err.(Error).Timeout() { - t.Fatalf("UnixListener.Accept failed: %v", err) + t.Fatal(err) } } else { c.Close() } if c, err := ln.AcceptUnix(); err != nil { if !err.(Error).Timeout() { - t.Fatalf("UnixListener.AcceptUnix failed: %v", err) + t.Fatal(err) } } else { c.Close() } if f, err := ln.File(); err != nil { - t.Fatalf("UnixListener.File failed: %v", err) + t.Fatal(err) } else { f.Close() } } func TestUnixConnSpecificMethods(t *testing.T) { - switch runtime.GOOS { - case "nacl", "plan9", "windows": - t.Skipf("skipping test on %q", runtime.GOOS) + if !testableNetwork("unixgram") { + t.Skip("unixgram test") } addr1, addr2, addr3 := testUnixAddr(), testUnixAddr(), testUnixAddr() a1, err := ResolveUnixAddr("unixgram", addr1) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c1, err := DialUnix("unixgram", a1, nil) if err != nil { - t.Fatalf("DialUnix failed: %v", err) + t.Fatal(err) } defer c1.Close() defer os.Remove(addr1) @@ -304,11 +268,11 @@ func TestUnixConnSpecificMethods(t *testing.T) { a2, err := ResolveUnixAddr("unixgram", addr2) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c2, err := DialUnix("unixgram", a2, nil) if err != nil { - t.Fatalf("DialUnix failed: %v", err) + t.Fatal(err) } defer c2.Close() defer os.Remove(addr2) @@ -322,11 +286,11 @@ func TestUnixConnSpecificMethods(t *testing.T) { a3, err := ResolveUnixAddr("unixgram", addr3) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c3, err := ListenUnixgram("unixgram", a3) if err != nil { - t.Fatalf("ListenUnixgram failed: %v", err) + t.Fatal(err) } defer c3.Close() defer os.Remove(addr3) @@ -343,39 +307,39 @@ func TestUnixConnSpecificMethods(t *testing.T) { rb2 := make([]byte, 128) rb3 := make([]byte, 128) if _, _, err := c1.WriteMsgUnix(wb, nil, a2); err != nil { - t.Fatalf("UnixConn.WriteMsgUnix failed: %v", err) + t.Fatal(err) } if _, _, _, _, err := c2.ReadMsgUnix(rb2, nil); err != nil { - t.Fatalf("UnixConn.ReadMsgUnix failed: %v", err) + t.Fatal(err) } if _, err := c2.WriteToUnix(wb, a1); err != nil { - t.Fatalf("UnixConn.WriteToUnix failed: %v", err) + t.Fatal(err) } if _, _, err := c1.ReadFromUnix(rb1); err != nil { - t.Fatalf("UnixConn.ReadFromUnix failed: %v", err) + t.Fatal(err) } if _, err := c3.WriteToUnix(wb, a1); err != nil { - t.Fatalf("UnixConn.WriteToUnix failed: %v", err) + t.Fatal(err) } if _, _, err := c1.ReadFromUnix(rb1); err != nil { - t.Fatalf("UnixConn.ReadFromUnix failed: %v", err) + t.Fatal(err) } if _, err := c2.WriteToUnix(wb, a3); err != nil { - t.Fatalf("UnixConn.WriteToUnix failed: %v", err) + t.Fatal(err) } if _, _, err := c3.ReadFromUnix(rb3); err != nil { - t.Fatalf("UnixConn.ReadFromUnix failed: %v", err) + t.Fatal(err) } if f, err := c1.File(); err != nil { - t.Fatalf("UnixConn.File failed: %v", err) + t.Fatal(err) } else { f.Close() } defer func() { if p := recover(); p != nil { - t.Fatalf("UnixConn.WriteToUnix or WriteMsgUnix panicked: %v", p) + t.Fatalf("panicked: %v", p) } }() diff --git a/libgo/go/net/rpc/client_test.go b/libgo/go/net/rpc/client_test.go index 5dd111b299f..ba11ff85869 100644 --- a/libgo/go/net/rpc/client_test.go +++ b/libgo/go/net/rpc/client_test.go @@ -54,7 +54,7 @@ func (s *S) Recv(nul *struct{}, reply *R) error { func TestGobError(t *testing.T) { if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/8908") + t.Skip("skipping test; see https://golang.org/issue/8908") } defer func() { err := recover() diff --git a/libgo/go/net/rpc/server.go b/libgo/go/net/rpc/server.go index 83728d55a18..6e6e8819174 100644 --- a/libgo/go/net/rpc/server.go +++ b/libgo/go/net/rpc/server.go @@ -13,6 +13,7 @@ Only methods that satisfy these criteria will be made available for remote access; other methods will be ignored: + - the method's type is exported. - the method is exported. - the method has two arguments, both exported (or builtin) types. - the method's second argument is a pointer. @@ -216,7 +217,7 @@ func isExportedOrBuiltinType(t reflect.Type) bool { // Register publishes in the server the set of methods of the // receiver value that satisfy the following conditions: -// - exported method +// - exported method of exported type // - two arguments, both of exported type // - the second argument is a pointer // - one return value, of type error diff --git a/libgo/go/net/sendfile_dragonfly.go b/libgo/go/net/sendfile_dragonfly.go index bc88fd3b907..a9cf3fe9517 100644 --- a/libgo/go/net/sendfile_dragonfly.go +++ b/libgo/go/net/sendfile_dragonfly.go @@ -91,13 +91,16 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { if err1 != nil { // This includes syscall.ENOSYS (no kernel // support) and syscall.EINVAL (fd types which - // don't implement sendfile together) - err = &OpError{"sendfile", c.net, c.raddr, err1} + // don't implement sendfile) + err = err1 break } } if lr != nil { lr.N = remain } + if err != nil { + err = os.NewSyscallError("sendfile", err) + } return written, err, written > 0 } diff --git a/libgo/go/net/sendfile_freebsd.go b/libgo/go/net/sendfile_freebsd.go index ffc147262a8..d0bf6034c18 100644 --- a/libgo/go/net/sendfile_freebsd.go +++ b/libgo/go/net/sendfile_freebsd.go @@ -91,13 +91,16 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { if err1 != nil { // This includes syscall.ENOSYS (no kernel // support) and syscall.EINVAL (fd types which - // don't implement sendfile together) - err = &OpError{"sendfile", c.net, c.raddr, err1} + // don't implement sendfile) + err = err1 break } } if lr != nil { lr.N = remain } + if err != nil { + err = os.NewSyscallError("sendfile", err) + } return written, err, written > 0 } diff --git a/libgo/go/net/sendfile_linux.go b/libgo/go/net/sendfile_linux.go index 5e117636a80..5ca41c39eb5 100644 --- a/libgo/go/net/sendfile_linux.go +++ b/libgo/go/net/sendfile_linux.go @@ -64,13 +64,16 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { if err1 != nil { // This includes syscall.ENOSYS (no kernel // support) and syscall.EINVAL (fd types which - // don't implement sendfile together) - err = &OpError{"sendfile", c.net, c.raddr, err1} + // don't implement sendfile) + err = err1 break } } if lr != nil { lr.N = remain } + if err != nil { + err = os.NewSyscallError("sendfile", err) + } return written, err, written > 0 } diff --git a/libgo/go/net/sendfile_solaris.go b/libgo/go/net/sendfile_solaris.go new file mode 100644 index 00000000000..0966575696b --- /dev/null +++ b/libgo/go/net/sendfile_solaris.go @@ -0,0 +1,110 @@ +// Copyright 2015 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" +) + +// Not strictly needed, but very helpful for debugging, see issue #10221. +//go:cgo_import_dynamic _ _ "libsendfile.so" +//go:cgo_import_dynamic _ _ "libsocket.so" + +// maxSendfileSize is the largest chunk size we ask the kernel to copy +// at a time. +const maxSendfileSize int = 4 << 20 + +// sendFile copies the contents of r to c using the sendfile +// system call to minimize copies. +// +// if handled == true, sendFile returns the number of bytes copied and any +// non-EOF error. +// +// if handled == false, sendFile performed no work. +func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { + // Solaris uses 0 as the "until EOF" value. If you pass in more bytes than the + // file contains, it will loop back to the beginning ad nauseam until it's sent + // exactly the number of bytes told to. As such, we need to know exactly how many + // bytes to send. + var remain int64 = 0 + + lr, ok := r.(*io.LimitedReader) + if ok { + remain, r = lr.N, lr.R + if remain <= 0 { + return 0, nil, true + } + } + f, ok := r.(*os.File) + if !ok { + return 0, nil, false + } + + if remain == 0 { + fi, err := f.Stat() + if err != nil { + return 0, err, false + } + + remain = fi.Size() + } + + // The other quirk with Solaris's sendfile implementation is that it doesn't + // use the current position of the file -- if you pass it offset 0, it starts + // from offset 0. There's no way to tell it "start from current position", so + // we have to manage that explicitly. + pos, err := f.Seek(0, os.SEEK_CUR) + if err != nil { + return 0, err, false + } + + if err := c.writeLock(); err != nil { + return 0, err, true + } + defer c.writeUnlock() + + dst := c.sysfd + src := int(f.Fd()) + for remain > 0 { + n := maxSendfileSize + if int64(n) > remain { + n = int(remain) + } + pos1 := pos + n, err1 := syscall.Sendfile(dst, src, &pos1, n) + if n > 0 { + pos += int64(n) + written += int64(n) + remain -= int64(n) + } + if n == 0 && err1 == nil { + break + } + if err1 == syscall.EAGAIN { + if err1 = c.pd.WaitWrite(); err1 == nil { + continue + } + } + if err1 == syscall.EINTR { + continue + } + if err1 != nil { + // This includes syscall.ENOSYS (no kernel + // support) and syscall.EINVAL (fd types which + // don't implement sendfile) + err = err1 + break + } + } + if lr != nil { + lr.N = remain + } + if err != nil { + err = os.NewSyscallError("sendfile", err) + } + return written, err, written > 0 +} diff --git a/libgo/go/net/sendfile_stub.go b/libgo/go/net/sendfile_stub.go index 03426ef0df1..a0760b4e526 100644 --- a/libgo/go/net/sendfile_stub.go +++ b/libgo/go/net/sendfile_stub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin nacl netbsd openbsd solaris +// +build darwin nacl netbsd openbsd package net diff --git a/libgo/go/net/sendfile_windows.go b/libgo/go/net/sendfile_windows.go index b128ba27b00..f3f3b54b886 100644 --- a/libgo/go/net/sendfile_windows.go +++ b/libgo/go/net/sendfile_windows.go @@ -46,7 +46,7 @@ func sendFile(fd *netFD, r io.Reader) (written int64, err error, handled bool) { return syscall.TransmitFile(o.fd.sysfd, o.handle, o.qty, 0, &o.o, nil, syscall.TF_WRITE_BEHIND) }) if err != nil { - return 0, err, false + return 0, os.NewSyscallError("transmitfile", err), false } if lr != nil { lr.N -= int64(done) diff --git a/libgo/go/net/server_test.go b/libgo/go/net/server_test.go index 6a2bb924329..fe0006b11fb 100644 --- a/libgo/go/net/server_test.go +++ b/libgo/go/net/server_test.go @@ -5,457 +5,384 @@ package net import ( - "flag" - "io" "os" - "runtime" "testing" - "time" ) -func skipServerTest(net, unixsotype, addr string, ipv6, ipv4map, linuxOnly bool) bool { - switch runtime.GOOS { - case "linux": - case "nacl", "plan9", "windows": - // "unix" sockets are not supported on Windows and Plan 9. - if net == unixsotype { - return true - } - default: - if net == unixsotype && linuxOnly { - return true - } - } - switch addr { - case "", "0.0.0.0", "[::ffff:0.0.0.0]", "[::]": - if testing.Short() || !*testExternal { - return true - } - } - if ipv6 && !supportsIPv6 { - return true - } - if ipv4map && !supportsIPv4map { - return true - } - return false -} - -var streamConnServerTests = []struct { - snet string // server side - saddr string - cnet string // client side - caddr string - ipv6 bool // test with underlying AF_INET6 socket - ipv4map bool // test with IPv6 IPv4-mapping functionality - empty bool // test with empty data - linuxOnly bool // test with abstract unix domain socket, a Linux-ism +var tcpServerTests = []struct { + snet, saddr string // server endpoint + tnet, taddr string // target endpoint for client }{ - {snet: "tcp", saddr: "", cnet: "tcp", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "0.0.0.0", cnet: "tcp", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "[::ffff:0.0.0.0]", cnet: "tcp", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "[::]", cnet: "tcp", caddr: "[::1]", ipv6: true}, + {snet: "tcp", saddr: ":0", tnet: "tcp", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "0.0.0.0:0", tnet: "tcp", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "[::ffff:0.0.0.0]:0", tnet: "tcp", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "[::]:0", tnet: "tcp", taddr: "::1"}, - {snet: "tcp", saddr: "", cnet: "tcp", caddr: "[::1]", ipv4map: true}, - {snet: "tcp", saddr: "0.0.0.0", cnet: "tcp", caddr: "[::1]", ipv4map: true}, - {snet: "tcp", saddr: "[::ffff:0.0.0.0]", cnet: "tcp", caddr: "[::1]", ipv4map: true}, - {snet: "tcp", saddr: "[::]", cnet: "tcp", caddr: "127.0.0.1", ipv4map: true}, + {snet: "tcp", saddr: ":0", tnet: "tcp", taddr: "::1"}, + {snet: "tcp", saddr: "0.0.0.0:0", tnet: "tcp", taddr: "::1"}, + {snet: "tcp", saddr: "[::ffff:0.0.0.0]:0", tnet: "tcp", taddr: "::1"}, + {snet: "tcp", saddr: "[::]:0", tnet: "tcp", taddr: "127.0.0.1"}, - {snet: "tcp", saddr: "", cnet: "tcp4", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "0.0.0.0", cnet: "tcp4", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "[::ffff:0.0.0.0]", cnet: "tcp4", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "[::]", cnet: "tcp6", caddr: "[::1]", ipv6: true}, + {snet: "tcp", saddr: ":0", tnet: "tcp4", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "0.0.0.0:0", tnet: "tcp4", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "[::ffff:0.0.0.0]:0", tnet: "tcp4", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "[::]:0", tnet: "tcp6", taddr: "::1"}, - {snet: "tcp", saddr: "", cnet: "tcp6", caddr: "[::1]", ipv4map: true}, - {snet: "tcp", saddr: "0.0.0.0", cnet: "tcp6", caddr: "[::1]", ipv4map: true}, - {snet: "tcp", saddr: "[::ffff:0.0.0.0]", cnet: "tcp6", caddr: "[::1]", ipv4map: true}, - {snet: "tcp", saddr: "[::]", cnet: "tcp4", caddr: "127.0.0.1", ipv4map: true}, + {snet: "tcp", saddr: ":0", tnet: "tcp6", taddr: "::1"}, + {snet: "tcp", saddr: "0.0.0.0:0", tnet: "tcp6", taddr: "::1"}, + {snet: "tcp", saddr: "[::ffff:0.0.0.0]:0", tnet: "tcp6", taddr: "::1"}, + {snet: "tcp", saddr: "[::]:0", tnet: "tcp4", taddr: "127.0.0.1"}, - {snet: "tcp", saddr: "127.0.0.1", cnet: "tcp", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "[::ffff:127.0.0.1]", cnet: "tcp", caddr: "127.0.0.1"}, - {snet: "tcp", saddr: "[::1]", cnet: "tcp", caddr: "[::1]", ipv6: true}, + {snet: "tcp", saddr: "127.0.0.1:0", tnet: "tcp", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "[::ffff:127.0.0.1]:0", tnet: "tcp", taddr: "127.0.0.1"}, + {snet: "tcp", saddr: "[::1]:0", tnet: "tcp", taddr: "::1"}, - {snet: "tcp4", saddr: "", cnet: "tcp4", caddr: "127.0.0.1"}, - {snet: "tcp4", saddr: "0.0.0.0", cnet: "tcp4", caddr: "127.0.0.1"}, - {snet: "tcp4", saddr: "[::ffff:0.0.0.0]", cnet: "tcp4", caddr: "127.0.0.1"}, + {snet: "tcp4", saddr: ":0", tnet: "tcp4", taddr: "127.0.0.1"}, + {snet: "tcp4", saddr: "0.0.0.0:0", tnet: "tcp4", taddr: "127.0.0.1"}, + {snet: "tcp4", saddr: "[::ffff:0.0.0.0]:0", tnet: "tcp4", taddr: "127.0.0.1"}, - {snet: "tcp4", saddr: "127.0.0.1", cnet: "tcp4", caddr: "127.0.0.1"}, + {snet: "tcp4", saddr: "127.0.0.1:0", tnet: "tcp4", taddr: "127.0.0.1"}, - {snet: "tcp6", saddr: "", cnet: "tcp6", caddr: "[::1]", ipv6: true}, - {snet: "tcp6", saddr: "[::]", cnet: "tcp6", caddr: "[::1]", ipv6: true}, + {snet: "tcp6", saddr: ":0", tnet: "tcp6", taddr: "::1"}, + {snet: "tcp6", saddr: "[::]:0", tnet: "tcp6", taddr: "::1"}, - {snet: "tcp6", saddr: "[::1]", cnet: "tcp6", caddr: "[::1]", ipv6: true}, - - {snet: "unix", saddr: testUnixAddr(), cnet: "unix", caddr: testUnixAddr()}, - {snet: "unix", saddr: "@gotest2/net", cnet: "unix", caddr: "@gotest2/net.local", linuxOnly: true}, + {snet: "tcp6", saddr: "[::1]:0", tnet: "tcp6", taddr: "::1"}, } -func TestStreamConnServer(t *testing.T) { - for _, tt := range streamConnServerTests { - if skipServerTest(tt.snet, "unix", tt.saddr, tt.ipv6, tt.ipv4map, tt.linuxOnly) { +// TestTCPServer tests concurrent accept-read-write servers. +func TestTCPServer(t *testing.T) { + const N = 3 + + for i, tt := range tcpServerTests { + if !testableListenArgs(tt.snet, tt.saddr, tt.taddr) { + t.Logf("skipping %s test", tt.snet+" "+tt.saddr+"->"+tt.taddr) continue } - listening := make(chan string) - done := make(chan int) - switch tt.snet { - case "tcp", "tcp4", "tcp6": - tt.saddr += ":0" - case "unix": - os.Remove(tt.saddr) - os.Remove(tt.caddr) + ln, err := Listen(tt.snet, tt.saddr) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) } - go runStreamConnServer(t, tt.snet, tt.saddr, listening, done) - taddr := <-listening // wait for server to start - - switch tt.cnet { - case "tcp", "tcp4", "tcp6": - _, port, err := SplitHostPort(taddr) + var lss []*localServer + var tpchs []chan error + defer func() { + for _, ls := range lss { + ls.teardown() + } + }() + for i := 0; i < N; i++ { + ls, err := (&streamListener{Listener: ln}).newLocalServer() if err != nil { - t.Fatalf("SplitHostPort(%q) failed: %v", taddr, err) + t.Fatal(err) + } + lss = append(lss, ls) + tpchs = append(tpchs, make(chan error, 1)) + } + for i := 0; i < N; i++ { + ch := tpchs[i] + handler := func(ls *localServer, ln Listener) { transponder(ln, ch) } + if err := lss[i].buildup(handler); err != nil { + t.Fatal(err) } - taddr = tt.caddr + ":" + port } - runStreamConnClient(t, tt.cnet, taddr, tt.empty) - <-done // make sure server stopped + var trchs []chan error + for i := 0; i < N; i++ { + _, port, err := SplitHostPort(lss[i].Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + d := Dialer{Timeout: someTimeout} + c, err := d.Dial(tt.tnet, JoinHostPort(tt.taddr, port)) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + defer c.Close() + trchs = append(trchs, make(chan error, 1)) + go transceiver(c, []byte("TCP SERVER TEST"), trchs[i]) + } - switch tt.snet { - case "unix": - os.Remove(tt.saddr) - os.Remove(tt.caddr) + for _, ch := range trchs { + for err := range ch { + t.Errorf("#%d: %v", i, err) + } + } + for _, ch := range tpchs { + for err := range ch { + t.Errorf("#%d: %v", i, err) + } } } } -var seqpacketConnServerTests = []struct { - net string - saddr string // server address - caddr string // client address - empty bool // test with empty data - linuxOnly bool // test with abstract unix domain socket, a Linux-ism +var unixAndUnixpacketServerTests = []struct { + network, address string }{ - {net: "unixpacket", saddr: testUnixAddr(), caddr: testUnixAddr()}, - {net: "unixpacket", saddr: "@gotest4/net", caddr: "@gotest4/net.local", linuxOnly: true}, + {"unix", testUnixAddr()}, + {"unix", "@nettest/go/unix"}, + + {"unixpacket", testUnixAddr()}, + {"unixpacket", "@nettest/go/unixpacket"}, } -func TestSeqpacketConnServer(t *testing.T) { - switch runtime.GOOS { - case "darwin", "nacl", "openbsd", "plan9", "windows": - fallthrough - case "freebsd": // FreeBSD 8 doesn't support unixpacket - t.Skipf("skipping test on %q", runtime.GOOS) - } +// TestUnixAndUnixpacketServer tests concurrent accept-read-write +// servers +func TestUnixAndUnixpacketServer(t *testing.T) { + const N = 3 - for _, tt := range seqpacketConnServerTests { - if runtime.GOOS != "linux" && tt.linuxOnly { + for i, tt := range unixAndUnixpacketServerTests { + if !testableListenArgs(tt.network, tt.address, "") { + t.Logf("skipping %s test", tt.network+" "+tt.address) continue } - listening := make(chan string) - done := make(chan int) - switch tt.net { - case "unixpacket": - os.Remove(tt.saddr) - os.Remove(tt.caddr) - } - go runStreamConnServer(t, tt.net, tt.saddr, listening, done) - taddr := <-listening // wait for server to start - - runStreamConnClient(t, tt.net, taddr, tt.empty) - <-done // make sure server stopped - - switch tt.net { - case "unixpacket": - os.Remove(tt.saddr) - os.Remove(tt.caddr) + ln, err := Listen(tt.network, tt.address) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) } - } -} -func runStreamConnServer(t *testing.T, net, laddr string, listening chan<- string, done chan<- int) { - defer close(done) - l, err := Listen(net, laddr) - if err != nil { - t.Errorf("Listen(%q, %q) failed: %v", net, laddr, err) - listening <- "<nil>" - return - } - defer l.Close() - listening <- l.Addr().String() - - echo := func(rw io.ReadWriter, done chan<- int) { - buf := make([]byte, 1024) - for { - n, err := rw.Read(buf[0:]) - if err != nil || n == 0 || string(buf[:n]) == "END" { - break + var lss []*localServer + var tpchs []chan error + defer func() { + for _, ls := range lss { + ls.teardown() } - rw.Write(buf[0:n]) + }() + for i := 0; i < N; i++ { + ls, err := (&streamListener{Listener: ln}).newLocalServer() + if err != nil { + t.Fatal(err) + } + lss = append(lss, ls) + tpchs = append(tpchs, make(chan error, 1)) } - close(done) - } - -run: - for { - c, err := l.Accept() - if err != nil { - t.Logf("Accept failed: %v", err) - continue run + for i := 0; i < N; i++ { + ch := tpchs[i] + handler := func(ls *localServer, ln Listener) { transponder(ln, ch) } + if err := lss[i].buildup(handler); err != nil { + t.Fatal(err) + } } - echodone := make(chan int) - go echo(c, echodone) - <-echodone // make sure echo stopped - c.Close() - break run - } -} - -func runStreamConnClient(t *testing.T, net, taddr string, isEmpty bool) { - c, err := Dial(net, taddr) - if err != nil { - t.Fatalf("Dial(%q, %q) failed: %v", net, taddr, err) - } - defer c.Close() - c.SetReadDeadline(time.Now().Add(1 * time.Second)) - var wb []byte - if !isEmpty { - wb = []byte("StreamConnClient by Dial\n") - } - if n, err := c.Write(wb); err != nil || n != len(wb) { - t.Fatalf("Write failed: %v, %v; want %v, <nil>", n, err, len(wb)) - } - - rb := make([]byte, 1024) - if n, err := c.Read(rb[0:]); err != nil || n != len(wb) { - t.Fatalf("Read failed: %v, %v; want %v, <nil>", n, err, len(wb)) - } + var trchs []chan error + for i := 0; i < N; i++ { + d := Dialer{Timeout: someTimeout} + c, err := d.Dial(lss[i].Listener.Addr().Network(), lss[i].Listener.Addr().String()) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + defer os.Remove(c.LocalAddr().String()) + defer c.Close() + trchs = append(trchs, make(chan error, 1)) + go transceiver(c, []byte("UNIX AND UNIXPACKET SERVER TEST"), trchs[i]) + } - // Send explicit ending for unixpacket. - // Older Linux kernels do not stop reads on close. - switch net { - case "unixpacket": - c.Write([]byte("END")) + for _, ch := range trchs { + for err := range ch { + t.Errorf("#%d: %v", i, err) + } + } + for _, ch := range tpchs { + for err := range ch { + t.Errorf("#%d: %v", i, err) + } + } } } -// Do not test empty datagrams by default. -// It causes unexplained timeouts on some systems, -// including Snow Leopard. I think that the kernel -// doesn't quite expect them. -var testDatagram = flag.Bool("datagram", false, "whether to test udp and unixgram") - -var datagramPacketConnServerTests = []struct { - snet string // server side - saddr string - cnet string // client side - caddr string - ipv6 bool // test with underlying AF_INET6 socket - ipv4map bool // test with IPv6 IPv4-mapping functionality - dial bool // test with Dial or DialUnix - empty bool // test with empty data - linuxOnly bool // test with abstract unix domain socket, a Linux-ism +var udpServerTests = []struct { + snet, saddr string // server endpoint + tnet, taddr string // target endpoint for client + dial bool // test with Dial }{ - {snet: "udp", saddr: "", cnet: "udp", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "0.0.0.0", cnet: "udp", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "[::ffff:0.0.0.0]", cnet: "udp", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "[::]", cnet: "udp", caddr: "[::1]", ipv6: true}, - - {snet: "udp", saddr: "", cnet: "udp", caddr: "[::1]", ipv4map: true}, - {snet: "udp", saddr: "0.0.0.0", cnet: "udp", caddr: "[::1]", ipv4map: true}, - {snet: "udp", saddr: "[::ffff:0.0.0.0]", cnet: "udp", caddr: "[::1]", ipv4map: true}, - {snet: "udp", saddr: "[::]", cnet: "udp", caddr: "127.0.0.1", ipv4map: true}, + {snet: "udp", saddr: ":0", tnet: "udp", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "0.0.0.0:0", tnet: "udp", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "[::ffff:0.0.0.0]:0", tnet: "udp", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "[::]:0", tnet: "udp", taddr: "::1"}, - {snet: "udp", saddr: "", cnet: "udp4", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "0.0.0.0", cnet: "udp4", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "[::ffff:0.0.0.0]", cnet: "udp4", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "[::]", cnet: "udp6", caddr: "[::1]", ipv6: true}, + {snet: "udp", saddr: ":0", tnet: "udp", taddr: "::1"}, + {snet: "udp", saddr: "0.0.0.0:0", tnet: "udp", taddr: "::1"}, + {snet: "udp", saddr: "[::ffff:0.0.0.0]:0", tnet: "udp", taddr: "::1"}, + {snet: "udp", saddr: "[::]:0", tnet: "udp", taddr: "127.0.0.1"}, - {snet: "udp", saddr: "", cnet: "udp6", caddr: "[::1]", ipv4map: true}, - {snet: "udp", saddr: "0.0.0.0", cnet: "udp6", caddr: "[::1]", ipv4map: true}, - {snet: "udp", saddr: "[::ffff:0.0.0.0]", cnet: "udp6", caddr: "[::1]", ipv4map: true}, - {snet: "udp", saddr: "[::]", cnet: "udp4", caddr: "127.0.0.1", ipv4map: true}, + {snet: "udp", saddr: ":0", tnet: "udp4", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "0.0.0.0:0", tnet: "udp4", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "[::ffff:0.0.0.0]:0", tnet: "udp4", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "[::]:0", tnet: "udp6", taddr: "::1"}, - {snet: "udp", saddr: "127.0.0.1", cnet: "udp", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "[::ffff:127.0.0.1]", cnet: "udp", caddr: "127.0.0.1"}, - {snet: "udp", saddr: "[::1]", cnet: "udp", caddr: "[::1]", ipv6: true}, + {snet: "udp", saddr: ":0", tnet: "udp6", taddr: "::1"}, + {snet: "udp", saddr: "0.0.0.0:0", tnet: "udp6", taddr: "::1"}, + {snet: "udp", saddr: "[::ffff:0.0.0.0]:0", tnet: "udp6", taddr: "::1"}, + {snet: "udp", saddr: "[::]:0", tnet: "udp4", taddr: "127.0.0.1"}, - {snet: "udp4", saddr: "", cnet: "udp4", caddr: "127.0.0.1"}, - {snet: "udp4", saddr: "0.0.0.0", cnet: "udp4", caddr: "127.0.0.1"}, - {snet: "udp4", saddr: "[::ffff:0.0.0.0]", cnet: "udp4", caddr: "127.0.0.1"}, + {snet: "udp", saddr: "127.0.0.1:0", tnet: "udp", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "[::ffff:127.0.0.1]:0", tnet: "udp", taddr: "127.0.0.1"}, + {snet: "udp", saddr: "[::1]:0", tnet: "udp", taddr: "::1"}, - {snet: "udp4", saddr: "127.0.0.1", cnet: "udp4", caddr: "127.0.0.1"}, + {snet: "udp4", saddr: ":0", tnet: "udp4", taddr: "127.0.0.1"}, + {snet: "udp4", saddr: "0.0.0.0:0", tnet: "udp4", taddr: "127.0.0.1"}, + {snet: "udp4", saddr: "[::ffff:0.0.0.0]:0", tnet: "udp4", taddr: "127.0.0.1"}, - {snet: "udp6", saddr: "", cnet: "udp6", caddr: "[::1]", ipv6: true}, - {snet: "udp6", saddr: "[::]", cnet: "udp6", caddr: "[::1]", ipv6: true}, + {snet: "udp4", saddr: "127.0.0.1:0", tnet: "udp4", taddr: "127.0.0.1"}, - {snet: "udp6", saddr: "[::1]", cnet: "udp6", caddr: "[::1]", ipv6: true}, + {snet: "udp6", saddr: ":0", tnet: "udp6", taddr: "::1"}, + {snet: "udp6", saddr: "[::]:0", tnet: "udp6", taddr: "::1"}, - {snet: "udp", saddr: "127.0.0.1", cnet: "udp", caddr: "127.0.0.1", dial: true}, - {snet: "udp", saddr: "127.0.0.1", cnet: "udp", caddr: "127.0.0.1", empty: true}, - {snet: "udp", saddr: "127.0.0.1", cnet: "udp", caddr: "127.0.0.1", dial: true, empty: true}, + {snet: "udp6", saddr: "[::1]:0", tnet: "udp6", taddr: "::1"}, - {snet: "udp", saddr: "[::1]", cnet: "udp", caddr: "[::1]", ipv6: true, dial: true}, - {snet: "udp", saddr: "[::1]", cnet: "udp", caddr: "[::1]", ipv6: true, empty: true}, - {snet: "udp", saddr: "[::1]", cnet: "udp", caddr: "[::1]", ipv6: true, dial: true, empty: true}, + {snet: "udp", saddr: "127.0.0.1:0", tnet: "udp", taddr: "127.0.0.1", dial: true}, - {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr()}, - {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr(), dial: true}, - {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr(), empty: true}, - {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr(), dial: true, empty: true}, - - {snet: "unixgram", saddr: "@gotest6/net", cnet: "unixgram", caddr: "@gotest6/net.local", linuxOnly: true}, + {snet: "udp", saddr: "[::1]:0", tnet: "udp", taddr: "::1", dial: true}, } -func TestDatagramPacketConnServer(t *testing.T) { - if !*testDatagram { - return - } - - for _, tt := range datagramPacketConnServerTests { - if skipServerTest(tt.snet, "unixgram", tt.saddr, tt.ipv6, tt.ipv4map, tt.linuxOnly) { +func TestUDPServer(t *testing.T) { + for i, tt := range udpServerTests { + if !testableListenArgs(tt.snet, tt.saddr, tt.taddr) { + t.Logf("skipping %s test", tt.snet+" "+tt.saddr+"->"+tt.taddr) continue } - listening := make(chan string) - done := make(chan int) - switch tt.snet { - case "udp", "udp4", "udp6": - tt.saddr += ":0" - case "unixgram": - os.Remove(tt.saddr) - os.Remove(tt.caddr) + c1, err := ListenPacket(tt.snet, tt.saddr) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) } - go runDatagramPacketConnServer(t, tt.snet, tt.saddr, listening, done) - taddr := <-listening // wait for server to start + ls, err := (&packetListener{PacketConn: c1}).newLocalServer() + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + tpch := make(chan error, 1) + handler := func(ls *localPacketServer, c PacketConn) { packetTransponder(c, tpch) } + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } - switch tt.cnet { - case "udp", "udp4", "udp6": - _, port, err := SplitHostPort(taddr) - if err != nil { - t.Fatalf("SplitHostPort(%q) failed: %v", taddr, err) - } - taddr = tt.caddr + ":" + port - tt.caddr += ":0" + trch := make(chan error, 1) + _, port, err := SplitHostPort(ls.PacketConn.LocalAddr().String()) + if err != nil { + t.Fatal(err) } if tt.dial { - runDatagramConnClient(t, tt.cnet, tt.caddr, taddr, tt.empty) + d := Dialer{Timeout: someTimeout} + c2, err := d.Dial(tt.tnet, JoinHostPort(tt.taddr, port)) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + defer c2.Close() + go transceiver(c2, []byte("UDP SERVER TEST"), trch) } else { - runDatagramPacketConnClient(t, tt.cnet, tt.caddr, taddr, tt.empty) + c2, err := ListenPacket(tt.tnet, JoinHostPort(tt.taddr, "0")) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + defer c2.Close() + dst, err := ResolveUDPAddr(tt.tnet, JoinHostPort(tt.taddr, port)) + if err != nil { + t.Fatal(err) + } + go packetTransceiver(c2, []byte("UDP SERVER TEST"), dst, trch) } - <-done // tell server to stop - <-done // make sure server stopped - switch tt.snet { - case "unixgram": - os.Remove(tt.saddr) - os.Remove(tt.caddr) + for err := range trch { + t.Errorf("#%d: %v", i, err) + } + for err := range tpch { + t.Errorf("#%d: %v", i, err) } } } -func runDatagramPacketConnServer(t *testing.T, net, laddr string, listening chan<- string, done chan<- int) { - c, err := ListenPacket(net, laddr) - if err != nil { - t.Errorf("ListenPacket(%q, %q) failed: %v", net, laddr, err) - listening <- "<nil>" - done <- 1 - return - } - defer c.Close() - listening <- c.LocalAddr().String() - - buf := make([]byte, 1024) -run: - for { - c.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) - n, ra, err := c.ReadFrom(buf[0:]) - if nerr, ok := err.(Error); ok && nerr.Timeout() { - select { - case done <- 1: - break run - default: - continue run - } - } - if err != nil { - break run - } - if _, err = c.WriteTo(buf[0:n], ra); err != nil { - t.Errorf("WriteTo(%v) failed: %v", ra, err) - break run - } - } - done <- 1 +var unixgramServerTests = []struct { + saddr string // server endpoint + caddr string // client endpoint + dial bool // test with Dial +}{ + {saddr: testUnixAddr(), caddr: testUnixAddr()}, + {saddr: testUnixAddr(), caddr: testUnixAddr(), dial: true}, + + {saddr: "@nettest/go/unixgram/server", caddr: "@nettest/go/unixgram/client"}, } -func runDatagramConnClient(t *testing.T, net, laddr, taddr string, isEmpty bool) { - var c Conn - var err error - switch net { - case "udp", "udp4", "udp6": - c, err = Dial(net, taddr) - if err != nil { - t.Fatalf("Dial(%q, %q) failed: %v", net, taddr, err) +func TestUnixgramServer(t *testing.T) { + for i, tt := range unixgramServerTests { + if !testableListenArgs("unixgram", tt.saddr, "") { + t.Logf("skipping %s test", "unixgram "+tt.saddr+"->"+tt.caddr) + continue } - case "unixgram": - c, err = DialUnix(net, &UnixAddr{Name: laddr, Net: net}, &UnixAddr{Name: taddr, Net: net}) + + c1, err := ListenPacket("unixgram", tt.saddr) if err != nil { - t.Fatalf("DialUnix(%q, {%q, %q}) failed: %v", net, laddr, taddr, err) + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) } - } - defer c.Close() - c.SetReadDeadline(time.Now().Add(1 * time.Second)) - var wb []byte - if !isEmpty { - wb = []byte("DatagramConnClient by Dial\n") - } - if n, err := c.Write(wb[0:]); err != nil || n != len(wb) { - t.Fatalf("Write failed: %v, %v; want %v, <nil>", n, err, len(wb)) - } - - rb := make([]byte, 1024) - if n, err := c.Read(rb[0:]); err != nil || n != len(wb) { - t.Fatalf("Read failed: %v, %v; want %v, <nil>", n, err, len(wb)) - } -} - -func runDatagramPacketConnClient(t *testing.T, net, laddr, taddr string, isEmpty bool) { - var ra Addr - var err error - switch net { - case "udp", "udp4", "udp6": - ra, err = ResolveUDPAddr(net, taddr) + ls, err := (&packetListener{PacketConn: c1}).newLocalServer() if err != nil { - t.Fatalf("ResolveUDPAddr(%q, %q) failed: %v", net, taddr, err) + t.Fatal(err) } - case "unixgram": - ra, err = ResolveUnixAddr(net, taddr) - if err != nil { - t.Fatalf("ResolveUxixAddr(%q, %q) failed: %v", net, taddr, err) + defer ls.teardown() + tpch := make(chan error, 1) + handler := func(ls *localPacketServer, c PacketConn) { packetTransponder(c, tpch) } + if err := ls.buildup(handler); err != nil { + t.Fatal(err) } - } - c, err := ListenPacket(net, laddr) - if err != nil { - t.Fatalf("ListenPacket(%q, %q) faild: %v", net, laddr, err) - } - defer c.Close() - c.SetReadDeadline(time.Now().Add(1 * time.Second)) - var wb []byte - if !isEmpty { - wb = []byte("DatagramPacketConnClient by ListenPacket\n") - } - if n, err := c.WriteTo(wb[0:], ra); err != nil || n != len(wb) { - t.Fatalf("WriteTo(%v) failed: %v, %v; want %v, <nil>", ra, n, err, len(wb)) - } + trch := make(chan error, 1) + if tt.dial { + d := Dialer{Timeout: someTimeout, LocalAddr: &UnixAddr{Net: "unixgram", Name: tt.caddr}} + c2, err := d.Dial("unixgram", ls.PacketConn.LocalAddr().String()) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + defer os.Remove(c2.LocalAddr().String()) + defer c2.Close() + go transceiver(c2, []byte(c2.LocalAddr().String()), trch) + } else { + c2, err := ListenPacket("unixgram", tt.caddr) + if err != nil { + if perr := parseDialError(err); perr != nil { + t.Error(perr) + } + t.Fatal(err) + } + defer os.Remove(c2.LocalAddr().String()) + defer c2.Close() + go packetTransceiver(c2, []byte("UNIXGRAM SERVER TEST"), ls.PacketConn.LocalAddr(), trch) + } - rb := make([]byte, 1024) - if n, _, err := c.ReadFrom(rb[0:]); err != nil || n != len(wb) { - t.Fatalf("ReadFrom failed: %v, %v; want %v, <nil>", n, err, len(wb)) + for err := range trch { + t.Errorf("#%d: %v", i, err) + } + for err := range tpch { + t.Errorf("#%d: %v", i, err) + } } } diff --git a/libgo/go/net/singleflight.go b/libgo/go/net/singleflight.go deleted file mode 100644 index bf599f0cc94..00000000000 --- a/libgo/go/net/singleflight.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2013 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 "sync" - -// call is an in-flight or completed singleflight.Do call -type call struct { - wg sync.WaitGroup - - // These fields are written once before the WaitGroup is done - // and are only read after the WaitGroup is done. - val interface{} - err error - - // These fields are read and written with the singleflight - // mutex held before the WaitGroup is done, and are read but - // not written after the WaitGroup is done. - dups int - chans []chan<- singleflightResult -} - -// singleflight represents a class of work and forms a namespace in -// which units of work can be executed with duplicate suppression. -type singleflight struct { - mu sync.Mutex // protects m - m map[string]*call // lazily initialized -} - -// singleflightResult holds the results of Do, so they can be passed -// on a channel. -type singleflightResult struct { - v interface{} - err error - shared bool -} - -// Do executes and returns the results of the given function, making -// sure that only one execution is in-flight for a given key at a -// time. If a duplicate comes in, the duplicate caller waits for the -// original to complete and receives the same results. -// The return value shared indicates whether v was given to multiple callers. -func (g *singleflight) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { - g.mu.Lock() - if g.m == nil { - g.m = make(map[string]*call) - } - if c, ok := g.m[key]; ok { - c.dups++ - g.mu.Unlock() - c.wg.Wait() - return c.val, c.err, true - } - c := new(call) - c.wg.Add(1) - g.m[key] = c - g.mu.Unlock() - - g.doCall(c, key, fn) - return c.val, c.err, c.dups > 0 -} - -// DoChan is like Do but returns a channel that will receive the -// results when they are ready. -func (g *singleflight) DoChan(key string, fn func() (interface{}, error)) <-chan singleflightResult { - ch := make(chan singleflightResult, 1) - g.mu.Lock() - if g.m == nil { - g.m = make(map[string]*call) - } - if c, ok := g.m[key]; ok { - c.dups++ - c.chans = append(c.chans, ch) - g.mu.Unlock() - return ch - } - c := &call{chans: []chan<- singleflightResult{ch}} - c.wg.Add(1) - g.m[key] = c - g.mu.Unlock() - - go g.doCall(c, key, fn) - - return ch -} - -// doCall handles the single call for a key. -func (g *singleflight) doCall(c *call, key string, fn func() (interface{}, error)) { - c.val, c.err = fn() - c.wg.Done() - - g.mu.Lock() - delete(g.m, key) - for _, ch := range c.chans { - ch <- singleflightResult{c.val, c.err, c.dups > 0} - } - g.mu.Unlock() -} - -// Forget tells the singleflight to forget about a key. Future calls -// to Do for this key will call the function rather than waiting for -// an earlier call to complete. -func (g *singleflight) Forget(key string) { - g.mu.Lock() - delete(g.m, key) - g.mu.Unlock() -} diff --git a/libgo/go/net/smtp/smtp.go b/libgo/go/net/smtp/smtp.go index 87dea442c46..09883503222 100644 --- a/libgo/go/net/smtp/smtp.go +++ b/libgo/go/net/smtp/smtp.go @@ -41,7 +41,7 @@ type Client struct { } // Dial returns a new Client connected to an SMTP server at addr. -// The addr must include a port number. +// The addr must include a port, as in "mail.example.com:smtp". func Dial(addr string) (*Client, error) { conn, err := net.Dial("tcp", addr) if err != nil { @@ -157,6 +157,17 @@ func (c *Client) StartTLS(config *tls.Config) error { return c.ehlo() } +// TLSConnectionState returns the client's TLS connection state. +// The return values are their zero values if StartTLS did +// not succeed. +func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) { + tc, ok := c.conn.(*tls.Conn) + if !ok { + return + } + return tc.ConnectionState(), true +} + // Verify checks the validity of an email address on the server. // If Verify returns nil, the address is valid. A non-nil return // does not necessarily indicate an invalid address. Many servers @@ -253,9 +264,9 @@ func (d *dataCloser) Close() error { } // Data issues a DATA command to the server and returns a writer that -// can be used to write the data. The caller should close the writer -// before calling any more methods on c. -// A call to Data must be preceded by one or more calls to Rcpt. +// can be used to write the mail headers and body. The caller should +// close the writer before calling any more methods on c. A call to +// Data must be preceded by one or more calls to Rcpt. func (c *Client) Data() (io.WriteCloser, error) { _, _, err := c.cmd(354, "DATA") if err != nil { @@ -270,6 +281,22 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests // possible, authenticates with the optional mechanism a if possible, // and then sends an email from address from, to addresses to, with // message msg. +// The addr must include a port, as in "mail.example.com:smtp". +// +// The addresses in the to parameter are the SMTP RCPT addresses. +// +// The msg parameter should be an RFC 822-style email with headers +// first, a blank line, and then the message body. The lines of msg +// should be CRLF terminated. The msg headers should usually include +// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" +// messages is accomplished by including an email address in the to +// parameter but not including it in the msg headers. +// +// The SendMail function and the the net/smtp package are low-level +// mechanisms and provide no support for DKIM signing, MIME +// attachments (see the mime/multipart package), or other mail +// functionality. Higher-level packages exist outside of the standard +// library. func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { c, err := Dial(addr) if err != nil { diff --git a/libgo/go/net/smtp/smtp_test.go b/libgo/go/net/smtp/smtp_test.go index 5c659e8a095..3ae0d5bf1dd 100644 --- a/libgo/go/net/smtp/smtp_test.go +++ b/libgo/go/net/smtp/smtp_test.go @@ -571,6 +571,50 @@ func TestTLSClient(t *testing.T) { } } +func TestTLSConnState(t *testing.T) { + ln := newLocalListener(t) + defer ln.Close() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer c.Close() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer c.Quit() + cfg := &tls.Config{ServerName: "example.com"} + testHookStartTLS(cfg) // set the RootCAs + if err := c.StartTLS(cfg); err != nil { + t.Errorf("StartTLS: %v", err) + return + } + cs, ok := c.TLSConnectionState() + if !ok { + t.Errorf("TLSConnectionState returned ok == false; want true") + return + } + if cs.Version == 0 || !cs.HandshakeComplete { + t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) + } + }() + <-clientDone + <-serverDone +} + func newLocalListener(t *testing.T) net.Listener { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { diff --git a/libgo/go/net/sock_cloexec.go b/libgo/go/net/sock_cloexec.go index dec81855b68..616a101eacb 100644 --- a/libgo/go/net/sock_cloexec.go +++ b/libgo/go/net/sock_cloexec.go @@ -9,34 +9,41 @@ package net -import "syscall" +import ( + "os" + "syscall" +) // Wrapper around the socket system call that marks the returned file // descriptor as nonblocking and close-on-exec. func sysSocket(family, sotype, proto int) (int, error) { - s, err := syscall.Socket(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto) + s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto) // On Linux the SOCK_NONBLOCK and SOCK_CLOEXEC flags were // introduced in 2.6.27 kernel and on FreeBSD both flags were // introduced in 10 kernel. If we get an EINVAL error on Linux // or EPROTONOSUPPORT error on FreeBSD, fall back to using // socket without them. - if err == nil || (err != syscall.EPROTONOSUPPORT && err != syscall.EINVAL) { - return s, err + switch err { + case nil: + return s, nil + default: + return -1, os.NewSyscallError("socket", err) + case syscall.EPROTONOSUPPORT, syscall.EINVAL: } // See ../syscall/exec_unix.go for description of ForkLock. syscall.ForkLock.RLock() - s, err = syscall.Socket(family, sotype, proto) + s, err = socketFunc(family, sotype, proto) if err == nil { syscall.CloseOnExec(s) } syscall.ForkLock.RUnlock() if err != nil { - return -1, err + return -1, os.NewSyscallError("socket", err) } if err = syscall.SetNonblock(s, true); err != nil { - syscall.Close(s) - return -1, err + closeFunc(s) + return -1, os.NewSyscallError("setnonblock", err) } return s, nil } @@ -44,14 +51,16 @@ func sysSocket(family, sotype, proto int) (int, error) { // Wrapper around the accept system call that marks the returned file // descriptor as nonblocking and close-on-exec. func accept(s int) (int, syscall.Sockaddr, error) { - ns, sa, err := syscall.Accept4(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC) + ns, sa, err := accept4Func(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC) // On Linux the accept4 system call was introduced in 2.6.28 // kernel and on FreeBSD it was introduced in 10 kernel. If we // get an ENOSYS error on both Linux and FreeBSD, or EINVAL // error on Linux, fall back to using accept. switch err { - default: // nil and errors other than the ones listed - return ns, sa, err + case nil: + return ns, sa, nil + default: // errors other than the ones listed + return -1, sa, os.NewSyscallError("accept4", err) case syscall.ENOSYS: // syscall missing case syscall.EINVAL: // some Linux use this instead of ENOSYS case syscall.EACCES: // some Linux use this instead of ENOSYS @@ -63,16 +72,16 @@ func accept(s int) (int, syscall.Sockaddr, error) { // because we have put fd.sysfd into non-blocking mode. // However, a call to the File method will put it back into // blocking mode. We can't take that risk, so no use of ForkLock here. - ns, sa, err = syscall.Accept(s) + ns, sa, err = acceptFunc(s) if err == nil { syscall.CloseOnExec(ns) } if err != nil { - return -1, nil, err + return -1, nil, os.NewSyscallError("accept", err) } if err = syscall.SetNonblock(ns, true); err != nil { - syscall.Close(ns) - return -1, nil, err + closeFunc(ns) + return -1, nil, os.NewSyscallError("setnonblock", err) } return ns, sa, nil } diff --git a/libgo/go/net/sock_posix.go b/libgo/go/net/sock_posix.go index 3f956df65a6..4d2cfde3f1c 100644 --- a/libgo/go/net/sock_posix.go +++ b/libgo/go/net/sock_posix.go @@ -17,8 +17,6 @@ import ( type sockaddr interface { Addr - netaddr - // family returns the platform-dependent address family // identifier. family() int @@ -42,11 +40,11 @@ func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr s return nil, err } if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil { - closesocket(s) + closeFunc(s) return nil, err } if fd, err = newFD(s, family, sotype, net); err != nil { - closesocket(s) + closeFunc(s) return nil, err } @@ -54,7 +52,7 @@ func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr s // following applications: // // - An endpoint holder that opens a passive stream - // connenction, known as a stream listener + // connection, known as a stream listener // // - An endpoint holder that opens a destination-unspecific // datagram connection, known as a datagram listener @@ -165,7 +163,7 @@ func (fd *netFD) listenStream(laddr sockaddr, backlog int) error { return os.NewSyscallError("bind", err) } } - if err := syscall.Listen(fd.sysfd, backlog); err != nil { + if err := listenFunc(fd.sysfd, backlog); err != nil { return os.NewSyscallError("listen", err) } if err := fd.init(); err != nil { diff --git a/libgo/go/net/sock_windows.go b/libgo/go/net/sock_windows.go index 6ccde3a24b9..888e70bb8e9 100644 --- a/libgo/go/net/sock_windows.go +++ b/libgo/go/net/sock_windows.go @@ -4,7 +4,10 @@ package net -import "syscall" +import ( + "os" + "syscall" +) func maxListenerBacklog() int { // TODO: Implement this @@ -12,13 +15,16 @@ func maxListenerBacklog() int { return syscall.SOMAXCONN } -func sysSocket(f, t, p int) (syscall.Handle, error) { +func sysSocket(family, sotype, proto int) (syscall.Handle, error) { // See ../syscall/exec_unix.go for description of ForkLock. syscall.ForkLock.RLock() - s, err := syscall.Socket(f, t, p) + s, err := socketFunc(family, sotype, proto) if err == nil { syscall.CloseOnExec(s) } syscall.ForkLock.RUnlock() - return s, err + if err != nil { + return syscall.InvalidHandle, os.NewSyscallError("socket", err) + } + return s, nil } diff --git a/libgo/go/net/sockopt_bsd.go b/libgo/go/net/sockopt_bsd.go index d5b3621c526..52b2a5debc5 100644 --- a/libgo/go/net/sockopt_bsd.go +++ b/libgo/go/net/sockopt_bsd.go @@ -25,7 +25,7 @@ func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_PORTRANGE, syscall.IPV6_PORTRANGE_HIGH) } } - if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW { + if supportsIPv4map && family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW { // Allow both IP versions even if the OS default // is otherwise. Note that some operating systems // never admit this option. diff --git a/libgo/go/net/sys_cloexec.go b/libgo/go/net/sys_cloexec.go index 898fb7c0c2c..ba266e6534c 100644 --- a/libgo/go/net/sys_cloexec.go +++ b/libgo/go/net/sys_cloexec.go @@ -9,24 +9,27 @@ package net -import "syscall" +import ( + "os" + "syscall" +) // Wrapper around the socket system call that marks the returned file // descriptor as nonblocking and close-on-exec. func sysSocket(family, sotype, proto int) (int, error) { // See ../syscall/exec_unix.go for description of ForkLock. syscall.ForkLock.RLock() - s, err := syscall.Socket(family, sotype, proto) + s, err := socketFunc(family, sotype, proto) if err == nil { syscall.CloseOnExec(s) } syscall.ForkLock.RUnlock() if err != nil { - return -1, err + return -1, os.NewSyscallError("socket", err) } if err = syscall.SetNonblock(s, true); err != nil { - syscall.Close(s) - return -1, err + closeFunc(s) + return -1, os.NewSyscallError("setnonblock", err) } return s, nil } @@ -39,16 +42,16 @@ func accept(s int) (int, syscall.Sockaddr, error) { // because we have put fd.sysfd into non-blocking mode. // However, a call to the File method will put it back into // blocking mode. We can't take that risk, so no use of ForkLock here. - ns, sa, err := syscall.Accept(s) + ns, sa, err := acceptFunc(s) if err == nil { syscall.CloseOnExec(ns) } if err != nil { - return -1, nil, err + return -1, nil, os.NewSyscallError("accept", err) } if err = syscall.SetNonblock(ns, true); err != nil { - syscall.Close(ns) - return -1, nil, err + closeFunc(ns) + return -1, nil, os.NewSyscallError("setnonblock", err) } return ns, sa, nil } diff --git a/libgo/go/net/tcp_test.go b/libgo/go/net/tcp_test.go index c04198ea000..25ae9b9d771 100644 --- a/libgo/go/net/tcp_test.go +++ b/libgo/go/net/tcp_test.go @@ -5,7 +5,6 @@ package net import ( - "fmt" "io" "reflect" "runtime" @@ -59,6 +58,8 @@ func BenchmarkTCP6PersistentTimeout(b *testing.B) { } func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { + testHookUninstaller.Do(uninstallTestHooks) + const msgLen = 512 conns := b.N numConcurrent := runtime.GOMAXPROCS(-1) * 2 @@ -76,7 +77,7 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { sendMsg := func(c Conn, buf []byte) bool { n, err := c.Write(buf) if n != len(buf) || err != nil { - b.Logf("Write failed: %v", err) + b.Log(err) return false } return true @@ -86,7 +87,7 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { n, err := c.Read(buf) read += n if err != nil { - b.Logf("Read failed: %v", err) + b.Log(err) return false } } @@ -94,7 +95,7 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { } ln, err := Listen("tcp", laddr) if err != nil { - b.Fatalf("Listen failed: %v", err) + b.Fatal(err) } defer ln.Close() serverSem := make(chan bool, numConcurrent) @@ -134,7 +135,7 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { }() c, err := Dial("tcp", ln.Addr().String()) if err != nil { - b.Logf("Dial failed: %v", err) + b.Log(err) return } defer c.Close() @@ -167,6 +168,8 @@ func BenchmarkTCP6ConcurrentReadWrite(b *testing.B) { } func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { + testHookUninstaller.Do(uninstallTestHooks) + // The benchmark creates GOMAXPROCS client/server pairs. // Each pair creates 4 goroutines: client reader/writer and server reader/writer. // The benchmark stresses concurrent reading and writing to the same connection. @@ -183,7 +186,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { servers := make([]Conn, P) ln, err := Listen("tcp", laddr) if err != nil { - b.Fatalf("Listen failed: %v", err) + b.Fatal(err) } defer ln.Close() done := make(chan bool) @@ -191,7 +194,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for p := 0; p < P; p++ { s, err := ln.Accept() if err != nil { - b.Errorf("Accept failed: %v", err) + b.Error(err) return } servers[p] = s @@ -201,7 +204,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for p := 0; p < P; p++ { c, err := Dial("tcp", ln.Addr().String()) if err != nil { - b.Fatalf("Dial failed: %v", err) + b.Fatal(err) } clients[p] = c } @@ -224,7 +227,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { buf[0] = v _, err := c.Write(buf[:]) if err != nil { - b.Errorf("Write failed: %v", err) + b.Error(err) return } } @@ -240,7 +243,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for i := 0; i < N; i++ { _, err := s.Read(buf[:]) if err != nil { - b.Errorf("Read failed: %v", err) + b.Error(err) return } pipe <- buf[0] @@ -259,7 +262,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { buf[0] = v _, err := s.Write(buf[:]) if err != nil { - b.Errorf("Write failed: %v", err) + b.Error(err) return } } @@ -273,7 +276,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for i := 0; i < N; i++ { _, err := c.Read(buf[:]) if err != nil { - b.Errorf("Read failed: %v", err) + b.Error(err) return } } @@ -284,7 +287,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { } type resolveTCPAddrTest struct { - net string + network string litAddrOrName string addr *TCPAddr err error @@ -294,8 +297,8 @@ var resolveTCPAddrTests = []resolveTCPAddrTest{ {"tcp", "127.0.0.1:0", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, {"tcp4", "127.0.0.1:65535", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 65535}, nil}, - {"tcp", "[::1]:1", &TCPAddr{IP: ParseIP("::1"), Port: 1}, nil}, - {"tcp6", "[::1]:65534", &TCPAddr{IP: ParseIP("::1"), Port: 65534}, nil}, + {"tcp", "[::1]:0", &TCPAddr{IP: ParseIP("::1"), Port: 0}, nil}, + {"tcp6", "[::1]:65535", &TCPAddr{IP: ParseIP("::1"), Port: 65535}, nil}, {"tcp", "[::1%en0]:1", &TCPAddr{IP: ParseIP("::1"), Port: 1, Zone: "en0"}, nil}, {"tcp6", "[::1%911]:2", &TCPAddr{IP: ParseIP("::1"), Port: 2, Zone: "911"}, nil}, @@ -308,41 +311,26 @@ var resolveTCPAddrTests = []resolveTCPAddrTest{ {"http", "127.0.0.1:0", nil, UnknownNetworkError("http")}, } -func init() { - if ifi := loopbackInterface(); ifi != nil { - index := fmt.Sprintf("%v", ifi.Index) - resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{ - {"tcp6", "[fe80::1%" + ifi.Name + "]:3", &TCPAddr{IP: ParseIP("fe80::1"), Port: 3, Zone: zoneToString(ifi.Index)}, nil}, - {"tcp6", "[fe80::1%" + index + "]:4", &TCPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: index}, nil}, - }...) - } - if ips, err := LookupIP("localhost"); err == nil && len(ips) > 1 && supportsIPv4 && supportsIPv6 { - resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{ - {"tcp", "localhost:5", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 5}, nil}, - {"tcp4", "localhost:6", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 6}, nil}, - {"tcp6", "localhost:7", &TCPAddr{IP: IPv6loopback, Port: 7}, nil}, - }...) - } -} - func TestResolveTCPAddr(t *testing.T) { - for _, tt := range resolveTCPAddrTests { - addr, err := ResolveTCPAddr(tt.net, tt.litAddrOrName) + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = lookupLocalhost + + for i, tt := range resolveTCPAddrTests { + addr, err := ResolveTCPAddr(tt.network, tt.litAddrOrName) if err != tt.err { - t.Fatalf("ResolveTCPAddr(%q, %q) failed: %v", tt.net, tt.litAddrOrName, err) + t.Errorf("#%d: %v", i, err) + } else if !reflect.DeepEqual(addr, tt.addr) { + t.Errorf("#%d: got %#v; want %#v", i, addr, tt.addr) } - if !reflect.DeepEqual(addr, tt.addr) { - t.Fatalf("ResolveTCPAddr(%q, %q) = %#v, want %#v", tt.net, tt.litAddrOrName, addr, tt.addr) + if err != nil { + continue } - if err == nil { - str := addr.String() - addr1, err := ResolveTCPAddr(tt.net, str) - if err != nil { - t.Fatalf("ResolveTCPAddr(%q, %q) [from %q]: %v", tt.net, str, tt.litAddrOrName, err) - } - if !reflect.DeepEqual(addr1, addr) { - t.Fatalf("ResolveTCPAddr(%q, %q) [from %q] = %#v, want %#v", tt.net, str, tt.litAddrOrName, addr1, addr) - } + rtaddr, err := ResolveTCPAddr(addr.Network(), addr.String()) + if err != nil { + t.Errorf("#%d: %v", i, err) + } else if !reflect.DeepEqual(rtaddr, addr) { + t.Errorf("#%d: got %#v; want %#v", i, rtaddr, addr) } } } @@ -358,13 +346,13 @@ var tcpListenerNameTests = []struct { func TestTCPListenerName(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } for _, tt := range tcpListenerNameTests { ln, err := ListenTCP(tt.net, tt.laddr) if err != nil { - t.Fatalf("ListenTCP failed: %v", err) + t.Fatal(err) } defer ln.Close() la := ln.Addr() @@ -376,59 +364,37 @@ func TestTCPListenerName(t *testing.T) { func TestIPv6LinkLocalUnicastTCP(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } if !supportsIPv6 { - t.Skip("ipv6 is not supported") - } - ifi := loopbackInterface() - if ifi == nil { - t.Skip("loopback interface not found") - } - laddr := ipv6LinkLocalUnicastAddr(ifi) - if laddr == "" { - t.Skip("ipv6 unicast address on loopback not found") + t.Skip("IPv6 is not supported") } - type test struct { - net, addr string - nameLookup bool - } - var tests = []test{ - {"tcp", "[" + laddr + "%" + ifi.Name + "]:0", false}, - {"tcp6", "[" + laddr + "%" + ifi.Name + "]:0", false}, - } - switch runtime.GOOS { - case "darwin", "freebsd", "openbsd", "netbsd": - tests = append(tests, []test{ - {"tcp", "[localhost%" + ifi.Name + "]:0", true}, - {"tcp6", "[localhost%" + ifi.Name + "]:0", true}, - }...) - case "linux": - tests = append(tests, []test{ - {"tcp", "[ip6-localhost%" + ifi.Name + "]:0", true}, - {"tcp6", "[ip6-localhost%" + ifi.Name + "]:0", true}, - }...) - } - for _, tt := range tests { - ln, err := Listen(tt.net, tt.addr) + for i, tt := range ipv6LinkLocalUnicastTCPTests { + ln, err := Listen(tt.network, tt.address) if err != nil { // It might return "LookupHost returned no // suitable address" error on some platforms. - t.Logf("Listen failed: %v", err) + t.Log(err) continue } - defer ln.Close() + ls, err := (&streamListener{Listener: ln}).newLocalServer() + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + ch := make(chan error, 1) + handler := func(ls *localServer, ln Listener) { transponder(ln, ch) } + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } if la, ok := ln.Addr().(*TCPAddr); !ok || !tt.nameLookup && la.Zone == "" { t.Fatalf("got %v; expected a proper address with zone identifier", la) } - done := make(chan int) - go transponder(t, ln, done) - - c, err := Dial(tt.net, ln.Addr().String()) + c, err := Dial(tt.network, ls.Listener.Addr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c.Close() if la, ok := c.LocalAddr().(*TCPAddr); !ok || !tt.nameLookup && la.Zone == "" { @@ -439,14 +405,16 @@ func TestIPv6LinkLocalUnicastTCP(t *testing.T) { } if _, err := c.Write([]byte("TCP OVER IPV6 LINKLOCAL TEST")); err != nil { - t.Fatalf("Conn.Write failed: %v", err) + t.Fatal(err) } b := make([]byte, 32) if _, err := c.Read(b); err != nil { - t.Fatalf("Conn.Read failed: %v", err) + t.Fatal(err) } - <-done + for err := range ch { + t.Errorf("#%d: %v", i, err) + } } } @@ -454,7 +422,7 @@ func TestTCPConcurrentAccept(t *testing.T) { defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) ln, err := Listen("tcp", "127.0.0.1:0") if err != nil { - t.Fatalf("Listen failed: %v", err) + t.Fatal(err) } const N = 10 var wg sync.WaitGroup @@ -492,13 +460,20 @@ func TestTCPConcurrentAccept(t *testing.T) { } } -func TestTCPReadWriteMallocs(t *testing.T) { - if testing.Short() { - t.Skip("skipping malloc count in short mode") +func TestTCPReadWriteAllocs(t *testing.T) { + t.Skip("skipping test on gccgo until escape analysis is turned on") + switch runtime.GOOS { + case "nacl", "windows": + // NaCl needs to allocate pseudo file descriptor + // stuff. See syscall/fd_nacl.go. + // Windows uses closures and channels for IO + // completion port-based netpoll. See fd_windows.go. + t.Skipf("not supported on %s", runtime.GOOS) } + ln, err := Listen("tcp", "127.0.0.1:0") if err != nil { - t.Fatalf("Listen failed: %v", err) + t.Fatal(err) } defer ln.Close() var server Conn @@ -510,25 +485,26 @@ func TestTCPReadWriteMallocs(t *testing.T) { }() client, err := Dial("tcp", ln.Addr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } + defer client.Close() if err := <-errc; err != nil { - t.Fatalf("Accept failed: %v", err) + t.Fatal(err) } defer server.Close() var buf [128]byte - mallocs := testing.AllocsPerRun(1000, func() { + allocs := testing.AllocsPerRun(1000, func() { _, err := server.Write(buf[:]) if err != nil { - t.Fatalf("Write failed: %v", err) + t.Fatal(err) } _, err = io.ReadFull(client, buf[:]) if err != nil { - t.Fatalf("Read failed: %v", err) + t.Fatal(err) } }) - if mallocs > 0 { - t.Fatalf("Got %v allocs, want 0", mallocs) + if allocs > 0 { + t.Fatalf("got %v; want 0", allocs) } } @@ -543,7 +519,7 @@ func TestTCPStress(t *testing.T) { sendMsg := func(c Conn, buf []byte) bool { n, err := c.Write(buf) if n != len(buf) || err != nil { - t.Logf("Write failed: %v", err) + t.Log(err) return false } return true @@ -553,7 +529,7 @@ func TestTCPStress(t *testing.T) { n, err := c.Read(buf) read += n if err != nil { - t.Logf("Read failed: %v", err) + t.Log(err) return false } } @@ -562,7 +538,7 @@ func TestTCPStress(t *testing.T) { ln, err := Listen("tcp", "127.0.0.1:0") if err != nil { - t.Fatalf("Listen failed: %v", err) + t.Fatal(err) } defer ln.Close() // Acceptor. @@ -593,7 +569,7 @@ func TestTCPStress(t *testing.T) { }() c, err := Dial("tcp", ln.Addr().String()) if err != nil { - t.Logf("Dial failed: %v", err) + t.Log(err) return } defer c.Close() diff --git a/libgo/go/net/tcpsock.go b/libgo/go/net/tcpsock.go index f3dfbd23d34..8765affd462 100644 --- a/libgo/go/net/tcpsock.go +++ b/libgo/go/net/tcpsock.go @@ -25,7 +25,14 @@ func (a *TCPAddr) String() string { return JoinHostPort(ip, itoa(a.Port)) } -func (a *TCPAddr) toAddr() Addr { +func (a *TCPAddr) isWildcard() bool { + if a == nil || a.IP == nil { + return true + } + return a.IP.IsUnspecified() +} + +func (a *TCPAddr) opAddr() Addr { if a == nil { return nil } @@ -46,9 +53,9 @@ func ResolveTCPAddr(net, addr string) (*TCPAddr, error) { default: return nil, UnknownNetworkError(net) } - a, err := resolveInternetAddr(net, addr, noDeadline) + addrs, err := internetAddrList(net, addr, noDeadline) if err != nil { return nil, err } - return a.toAddr().(*TCPAddr), nil + return addrs.first(isIPv4).(*TCPAddr), nil } diff --git a/libgo/go/net/tcpsock_plan9.go b/libgo/go/net/tcpsock_plan9.go index 52019d7b4eb..9f23703abb4 100644 --- a/libgo/go/net/tcpsock_plan9.go +++ b/libgo/go/net/tcpsock_plan9.go @@ -23,7 +23,11 @@ func newTCPConn(fd *netFD) *TCPConn { // ReadFrom implements the io.ReaderFrom ReadFrom method. func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) { - return genericReadFrom(c, r) + n, err := genericReadFrom(c, r) + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, err } // CloseRead shuts down the reading side of the TCP connection. @@ -32,7 +36,11 @@ func (c *TCPConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return c.fd.closeRead() + err := c.fd.closeRead() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // CloseWrite shuts down the writing side of the TCP connection. @@ -41,7 +49,11 @@ func (c *TCPConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } - return c.fd.closeWrite() + err := c.fd.closeWrite() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // SetLinger sets the behavior of Close on a connection which still @@ -57,7 +69,7 @@ func (c *TCPConn) CloseWrite() error { // some operating systems after sec seconds have elapsed any remaining // unsent data may be discarded. func (c *TCPConn) SetLinger(sec int) error { - return syscall.EPLAN9 + return &OpError{Op: "set", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // SetKeepAlive sets whether the operating system should send @@ -66,7 +78,10 @@ func (c *TCPConn) SetKeepAlive(keepalive bool) error { if !c.ok() { return syscall.EPLAN9 } - return setKeepAlive(c.fd, keepalive) + if err := setKeepAlive(c.fd, keepalive); err != nil { + return &OpError{Op: "set", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return nil } // SetKeepAlivePeriod sets period between keep alives. @@ -74,7 +89,10 @@ func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error { if !c.ok() { return syscall.EPLAN9 } - return setKeepAlivePeriod(c.fd, d) + if err := setKeepAlivePeriod(c.fd, d); err != nil { + return &OpError{Op: "set", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return nil } // SetNoDelay controls whether the operating system should delay @@ -82,7 +100,7 @@ func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error { // 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 + return &OpError{Op: "set", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // DialTCP connects to the remote address raddr on the network net, @@ -99,10 +117,10 @@ func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, e switch net { case "tcp", "tcp4", "tcp6": default: - return nil, &OpError{"dial", net, raddr, UnknownNetworkError(net)} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(net)} } if raddr == nil { - return nil, &OpError{"dial", net, nil, errMissingAddress} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress} } fd, err := dialPlan9(net, laddr, raddr) if err != nil { @@ -151,12 +169,18 @@ func (l *TCPListener) Close() error { } if _, err := l.fd.ctl.WriteString("hangup"); err != nil { l.fd.ctl.Close() - return &OpError{"close", l.fd.ctl.Name(), l.fd.laddr, err} + return &OpError{Op: "close", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: err} } - return l.fd.ctl.Close() + err := l.fd.ctl.Close() + if err != nil { + err = &OpError{Op: "close", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: err} + } + return err } // Addr returns the listener's network address, a *TCPAddr. +// The Addr returned is shared by all invocations of Addr, so +// do not modify it. func (l *TCPListener) Addr() Addr { return l.fd.laddr } // SetDeadline sets the deadline associated with the listener. @@ -165,7 +189,10 @@ func (l *TCPListener) SetDeadline(t time.Time) error { if l == nil || l.fd == nil || l.fd.ctl == nil { return syscall.EINVAL } - return l.fd.setDeadline(t) + if err := l.fd.setDeadline(t); err != nil { + return &OpError{Op: "set", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: err} + } + return nil } // File returns a copy of the underlying os.File, set to blocking @@ -175,7 +202,13 @@ func (l *TCPListener) SetDeadline(t time.Time) error { // The returned os.File's file descriptor is different from the // connection's. Attempting to change properties of the original // using this duplicate may or may not have the desired effect. -func (l *TCPListener) File() (f *os.File, err error) { return l.dup() } +func (l *TCPListener) File() (f *os.File, err error) { + f, err = l.dup() + if err != nil { + err = &OpError{Op: "file", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: err} + } + return +} // ListenTCP announces on the TCP address laddr and returns a TCP // listener. Net must be "tcp", "tcp4", or "tcp6". If laddr has a @@ -185,7 +218,7 @@ func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { switch net { case "tcp", "tcp4", "tcp6": default: - return nil, &OpError{"listen", net, laddr, UnknownNetworkError(net)} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(net)} } if laddr == nil { laddr = &TCPAddr{} diff --git a/libgo/go/net/tcpsock_posix.go b/libgo/go/net/tcpsock_posix.go index dd78aefa773..7e49b769e1c 100644 --- a/libgo/go/net/tcpsock_posix.go +++ b/libgo/go/net/tcpsock_posix.go @@ -13,11 +13,6 @@ import ( "time" ) -// BUG(rsc): On OpenBSD, listening on the "tcp" network does not listen for -// both IPv4 and IPv6 connections. This is due to the fact that IPv4 traffic -// will not be routed to an IPv6 socket - two separate sockets are required -// if both AFs are to be supported. See inet6(4) on OpenBSD for details. - func sockaddrToTCP(sa syscall.Sockaddr) Addr { switch sa := sa.(type) { case *syscall.SockaddrInet4: @@ -38,13 +33,6 @@ func (a *TCPAddr) family() int { return syscall.AF_INET6 } -func (a *TCPAddr) isWildcard() bool { - if a == nil || a.IP == nil { - return true - } - return a.IP.IsUnspecified() -} - func (a *TCPAddr) sockaddr(family int) (syscall.Sockaddr, error) { if a == nil { return nil, nil @@ -60,16 +48,23 @@ type TCPConn struct { func newTCPConn(fd *netFD) *TCPConn { c := &TCPConn{conn{fd}} - c.SetNoDelay(true) + setNoDelay(c.fd, true) return c } // ReadFrom implements the io.ReaderFrom ReadFrom method. func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) { if n, err, handled := sendFile(c.fd, r); handled { + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } return n, err } - return genericReadFrom(c, r) + n, err := genericReadFrom(c, r) + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, err } // CloseRead shuts down the reading side of the TCP connection. @@ -78,7 +73,11 @@ func (c *TCPConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return c.fd.closeRead() + err := c.fd.closeRead() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // CloseWrite shuts down the writing side of the TCP connection. @@ -87,7 +86,11 @@ func (c *TCPConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } - return c.fd.closeWrite() + err := c.fd.closeWrite() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // SetLinger sets the behavior of Close on a connection which still @@ -106,7 +109,10 @@ func (c *TCPConn) SetLinger(sec int) error { if !c.ok() { return syscall.EINVAL } - return setLinger(c.fd, sec) + if err := setLinger(c.fd, sec); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return nil } // SetKeepAlive sets whether the operating system should send @@ -115,7 +121,10 @@ func (c *TCPConn) SetKeepAlive(keepalive bool) error { if !c.ok() { return syscall.EINVAL } - return setKeepAlive(c.fd, keepalive) + if err := setKeepAlive(c.fd, keepalive); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return nil } // SetKeepAlivePeriod sets period between keep alives. @@ -123,7 +132,10 @@ func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error { if !c.ok() { return syscall.EINVAL } - return setKeepAlivePeriod(c.fd, d) + if err := setKeepAlivePeriod(c.fd, d); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return nil } // SetNoDelay controls whether the operating system should delay @@ -134,7 +146,10 @@ func (c *TCPConn) SetNoDelay(noDelay bool) error { if !c.ok() { return syscall.EINVAL } - return setNoDelay(c.fd, noDelay) + if err := setNoDelay(c.fd, noDelay); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return nil } // DialTCP connects to the remote address raddr on the network net, @@ -144,10 +159,10 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) { switch net { case "tcp", "tcp4", "tcp6": default: - return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(net)} } if raddr == nil { - return nil, &OpError{Op: "dial", Net: net, Addr: nil, Err: errMissingAddress} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress} } return dialTCP(net, laddr, raddr, noDeadline) } @@ -170,7 +185,7 @@ func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, e // see this happen, rather than expose the buggy effect to users, we // close the fd and try again. If it happens twice more, we relent and // use the result. See also: - // http://golang.org/issue/2690 + // https://golang.org/issue/2690 // http://stackoverflow.com/questions/4949858/ // // The opposite can also happen: if we ask the kernel to pick an appropriate @@ -187,7 +202,7 @@ func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, e } if err != nil { - return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: err} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err} } return newTCPConn(fd), nil } @@ -215,8 +230,13 @@ func selfConnect(fd *netFD, err error) bool { } func spuriousENOTAVAIL(err error) bool { - e, ok := err.(*OpError) - return ok && e.Err == syscall.EADDRNOTAVAIL + if op, ok := err.(*OpError); ok { + err = op.Err + } + if sys, ok := err.(*os.SyscallError); ok { + err = sys.Err + } + return err == syscall.EADDRNOTAVAIL } // TCPListener is a TCP network listener. Clients should typically @@ -233,7 +253,7 @@ func (l *TCPListener) AcceptTCP() (*TCPConn, error) { } fd, err := l.fd.accept() if err != nil { - return nil, err + return nil, &OpError{Op: "accept", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} } return newTCPConn(fd), nil } @@ -254,10 +274,16 @@ func (l *TCPListener) Close() error { if l == nil || l.fd == nil { return syscall.EINVAL } - return l.fd.Close() + err := l.fd.Close() + if err != nil { + err = &OpError{Op: "close", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} + } + return err } // Addr returns the listener's network address, a *TCPAddr. +// The Addr returned is shared by all invocations of Addr, so +// do not modify it. func (l *TCPListener) Addr() Addr { return l.fd.laddr } // SetDeadline sets the deadline associated with the listener. @@ -266,7 +292,10 @@ func (l *TCPListener) SetDeadline(t time.Time) error { if l == nil || l.fd == nil { return syscall.EINVAL } - return l.fd.setDeadline(t) + if err := l.fd.setDeadline(t); err != nil { + return &OpError{Op: "set", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} + } + return nil } // File returns a copy of the underlying os.File, set to blocking @@ -276,7 +305,13 @@ func (l *TCPListener) SetDeadline(t time.Time) error { // The returned os.File's file descriptor is different from the // connection's. Attempting to change properties of the original // using this duplicate may or may not have the desired effect. -func (l *TCPListener) File() (f *os.File, err error) { return l.fd.dup() } +func (l *TCPListener) File() (f *os.File, err error) { + f, err = l.fd.dup() + if err != nil { + err = &OpError{Op: "file", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} + } + return +} // ListenTCP announces on the TCP address laddr and returns a TCP // listener. Net must be "tcp", "tcp4", or "tcp6". If laddr has a @@ -286,14 +321,14 @@ func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { switch net { case "tcp", "tcp4", "tcp6": default: - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(net)} } if laddr == nil { laddr = &TCPAddr{} } fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_STREAM, 0, "listen") if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err} } return &TCPListener{fd}, nil } diff --git a/libgo/go/net/tcpsockopt_plan9.go b/libgo/go/net/tcpsockopt_plan9.go index 0e7a6647caf..9abe186cecb 100644 --- a/libgo/go/net/tcpsockopt_plan9.go +++ b/libgo/go/net/tcpsockopt_plan9.go @@ -7,12 +7,13 @@ package net import ( + "strconv" "time" ) // Set keep alive period. func setKeepAlivePeriod(fd *netFD, d time.Duration) error { - cmd := "keepalive " + string(int64(d/time.Millisecond)) + cmd := "keepalive " + strconv.Itoa(int(d/time.Millisecond)) _, e := fd.ctl.WriteAt([]byte(cmd), 0) return e } diff --git a/libgo/go/net/tcpsockopt_solaris.go b/libgo/go/net/tcpsockopt_solaris.go index eaab6b6787b..31f5df0526f 100644 --- a/libgo/go/net/tcpsockopt_solaris.go +++ b/libgo/go/net/tcpsockopt_solaris.go @@ -1,9 +1,7 @@ -// Copyright 2013 The Go Authors. All rights reserved. +// Copyright 2015 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. -// TCP socket options for solaris - package net import ( @@ -12,16 +10,26 @@ import ( "time" ) -// Set keep alive period. func setKeepAlivePeriod(fd *netFD, d time.Duration) error { if err := fd.incref(); err != nil { return err } defer fd.decref() + // The kernel expects milliseconds so round to next highest + // millisecond. + d += (time.Millisecond - time.Nanosecond) + msecs := int(d / time.Millisecond) - // The kernel expects seconds so round to next highest second. - d += (time.Second - time.Nanosecond) - secs := int(d.Seconds()) + // Normally we'd do + // syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs) + // here, but we can't because Solaris does not have TCP_KEEPINTVL. + // Solaris has TCP_KEEPALIVE_ABORT_THRESHOLD, but it's not the same + // thing, it refers to the total time until aborting (not between + // probes), and it uses an exponential backoff algorithm instead of + // waiting the same time between probes. We can't hope for the best + // and do it anyway, like on Darwin, because Solaris might eventually + // allocate a constant with a different meaning for the value of + // TCP_KEEPINTVL on illumos. - return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.SO_KEEPALIVE, secs)) + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE_THRESHOLD, msecs)) } diff --git a/libgo/go/net/tcpsockopt_unix.go b/libgo/go/net/tcpsockopt_unix.go index c9f604cad7b..c8970d1b574 100644 --- a/libgo/go/net/tcpsockopt_unix.go +++ b/libgo/go/net/tcpsockopt_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build freebsd linux netbsd solaris +// +build freebsd linux netbsd package net diff --git a/libgo/go/net/tcpsockopt_windows.go b/libgo/go/net/tcpsockopt_windows.go index 091f5233f20..ae2d7c8f182 100644 --- a/libgo/go/net/tcpsockopt_windows.go +++ b/libgo/go/net/tcpsockopt_windows.go @@ -28,5 +28,5 @@ func setKeepAlivePeriod(fd *netFD, d time.Duration) error { ret := uint32(0) size := uint32(unsafe.Sizeof(ka)) err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0) - return os.NewSyscallError("WSAIoctl", err) + return os.NewSyscallError("wsaioctl", err) } diff --git a/libgo/go/net/testdata/ipv4-hosts b/libgo/go/net/testdata/ipv4-hosts new file mode 100644 index 00000000000..5208bb44ac8 --- /dev/null +++ b/libgo/go/net/testdata/ipv4-hosts @@ -0,0 +1,12 @@ +# See https://tools.ietf.org/html/rfc1123. +# +# The literal IPv4 address parser in the net package is a relaxed +# one. It may accept a literal IPv4 address in dotted-decimal notation +# with leading zeros such as "001.2.003.4". + +# internet address and host name +127.0.0.1 localhost # inline comment separated by tab +127.000.000.002 localhost # inline comment separated by space + +# internet address, host name and aliases +127.000.000.003 localhost localhost.localdomain diff --git a/libgo/go/net/testdata/ipv6-hosts b/libgo/go/net/testdata/ipv6-hosts new file mode 100644 index 00000000000..f78b7fcf19e --- /dev/null +++ b/libgo/go/net/testdata/ipv6-hosts @@ -0,0 +1,11 @@ +# See https://tools.ietf.org/html/rfc5952, https://tools.ietf.org/html/rfc4007. + +# internet address and host name +::1 localhost # inline comment separated by tab +fe80:0000:0000:0000:0000:0000:0000:0001 localhost # inline comment separated by space + +# internet address with zone identifier and host name +fe80:0000:0000:0000:0000:0000:0000:0002%lo0 localhost + +# internet address, host name and aliases +fe80::3%lo0 localhost localhost.localdomain diff --git a/libgo/go/net/testdata/openbsd-resolv.conf b/libgo/go/net/testdata/openbsd-resolv.conf new file mode 100644 index 00000000000..8281a91b4a2 --- /dev/null +++ b/libgo/go/net/testdata/openbsd-resolv.conf @@ -0,0 +1,5 @@ +# Generated by vio0 dhclient +search c.symbolic-datum-552.internal. +nameserver 169.254.169.254 +nameserver 10.240.0.1 +lookup file bind diff --git a/libgo/go/net/testdata/hosts_singleline b/libgo/go/net/testdata/singleline-hosts index 5f5f74a3fad..5f5f74a3fad 100644 --- a/libgo/go/net/testdata/hosts_singleline +++ b/libgo/go/net/testdata/singleline-hosts diff --git a/libgo/go/net/textproto/reader.go b/libgo/go/net/textproto/reader.go index eea9207f252..91303fec612 100644 --- a/libgo/go/net/textproto/reader.go +++ b/libgo/go/net/textproto/reader.go @@ -13,10 +13,6 @@ import ( "strings" ) -// BUG(rsc): To let callers manage exposure to denial of service -// attacks, Reader should allow them to set and reset a limit on -// the number of bytes read from the connection. - // A Reader implements convenience methods for reading requests // or responses from a text protocol network connection. type Reader struct { @@ -26,6 +22,10 @@ type Reader struct { } // NewReader returns a new Reader reading from r. +// +// To avoid denial of service attacks, the provided bufio.Reader +// should be reading from an io.LimitReader or similar Reader to bound +// the size of responses. func NewReader(r *bufio.Reader) *Reader { return &Reader{R: r} } @@ -485,6 +485,13 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { } key := canonicalMIMEHeaderKey(kv[:endKey]) + // As per RFC 7230 field-name is a token, tokens consist of one or more chars. + // We could return a ProtocolError here, but better to be liberal in what we + // accept, so if we get an empty key, skip it. + if key == "" { + continue + } + // Skip initial spaces in value. i++ // skip colon for i < len(kv) && (kv[i] == ' ' || kv[i] == '\t') { @@ -540,11 +547,16 @@ func (r *Reader) upcomingHeaderNewlines() (n int) { // 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. +// If s contains a space or invalid header field bytes, it is +// returned without modifications. func CanonicalMIMEHeaderKey(s string) string { // Quick check for canonical encoding. upper := true for i := 0; i < len(s); i++ { c := s[i] + if !validHeaderFieldByte(c) { + return s + } if upper && 'a' <= c && c <= 'z' { return canonicalMIMEHeaderKey([]byte(s)) } @@ -558,19 +570,44 @@ func CanonicalMIMEHeaderKey(s string) string { const toLower = 'a' - 'A' +// validHeaderFieldByte reports whether b is a valid byte in a header +// field key. This is actually stricter than RFC 7230, which says: +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +// token = 1*tchar +// TODO: revisit in Go 1.6+ and possibly expand this. But note that many +// servers have historically dropped '_' to prevent ambiguities when mapping +// to CGI environment variables. +func validHeaderFieldByte(b byte) bool { + return ('A' <= b && b <= 'Z') || + ('a' <= b && b <= 'z') || + ('0' <= b && b <= '9') || + b == '-' +} + // canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is // allowed to mutate the provided byte slice before returning the // string. +// +// For invalid inputs (if a contains spaces or non-token bytes), a +// is unchanged and a string copy is returned. func canonicalMIMEHeaderKey(a []byte) string { + // See if a looks like a header key. If not, return it unchanged. + for _, c := range a { + if validHeaderFieldByte(c) { + continue + } + // Don't canonicalize. + return string(a) + } + upper := true for i, c := range a { // 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 c == ' ' { - c = '-' - } else if upper && 'a' <= c && c <= 'z' { + if upper && 'a' <= c && c <= 'z' { c -= toLower } else if !upper && 'A' <= c && c <= 'Z' { c += toLower diff --git a/libgo/go/net/textproto/reader_test.go b/libgo/go/net/textproto/reader_test.go index c89566635e1..91550f74934 100644 --- a/libgo/go/net/textproto/reader_test.go +++ b/libgo/go/net/textproto/reader_test.go @@ -24,11 +24,14 @@ var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{ {"uSER-aGENT", "User-Agent"}, {"user-agent", "User-Agent"}, {"USER-AGENT", "User-Agent"}, - {"üser-agenT", "üser-Agent"}, // non-ASCII unchanged + + // Non-ASCII or anything with spaces or non-token chars is unchanged: + {"üser-agenT", "üser-agenT"}, + {"a B", "a B"}, // This caused a panic due to mishandling of a space: - {"C Ontent-Transfer-Encoding", "C-Ontent-Transfer-Encoding"}, - {"foo bar", "Foo-Bar"}, + {"C Ontent-Transfer-Encoding", "C Ontent-Transfer-Encoding"}, + {"foo bar", "foo bar"}, } func TestCanonicalMIMEHeaderKey(t *testing.T) { @@ -153,6 +156,15 @@ func TestReadMIMEHeaderSingle(t *testing.T) { } } +func TestReadMIMEHeaderNoKey(t *testing.T) { + r := reader(": bar\ntest-1: 1\n\n") + m, err := r.ReadMIMEHeader() + want := MIMEHeader{"Test-1": {"1"}} + if !reflect.DeepEqual(m, want) || err != nil { + t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want) + } +} + func TestLargeReadMIMEHeader(t *testing.T) { data := make([]byte, 16*1024) for i := 0; i < len(data); i++ { @@ -185,7 +197,7 @@ func TestReadMIMEHeaderNonCompliant(t *testing.T) { "Foo": {"bar"}, "Content-Language": {"en"}, "Sid": {"0"}, - "Audio-Mode": {"None"}, + "Audio Mode": {"None"}, "Privilege": {"127"}, } if !reflect.DeepEqual(m, want) || err != nil { diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go index 9ef0c4d15cc..ca94e24c816 100644 --- a/libgo/go/net/timeout_test.go +++ b/libgo/go/net/timeout_test.go @@ -8,408 +8,727 @@ import ( "fmt" "io" "io/ioutil" + "net/internal/socktest" "runtime" + "sync" "testing" "time" ) -func isTimeout(err error) bool { - e, ok := err.(Error) - return ok && e.Timeout() +var dialTimeoutTests = []struct { + timeout time.Duration + delta time.Duration // for deadline + + guard time.Duration + max time.Duration +}{ + // Tests that dial timeouts, deadlines in the past work. + {-5 * time.Second, 0, -5 * time.Second, 100 * time.Millisecond}, + {0, -5 * time.Second, -5 * time.Second, 100 * time.Millisecond}, + {-5 * time.Second, 5 * time.Second, -5 * time.Second, 100 * time.Millisecond}, // timeout over deadline + + {50 * time.Millisecond, 0, 100 * time.Millisecond, time.Second}, + {0, 50 * time.Millisecond, 100 * time.Millisecond, time.Second}, + {50 * time.Millisecond, 5 * time.Second, 100 * time.Millisecond, time.Second}, // timeout over deadline } -type copyRes struct { - n int64 - err error - d time.Duration +func TestDialTimeout(t *testing.T) { + origTestHookDialChannel := testHookDialChannel + defer func() { testHookDialChannel = origTestHookDialChannel }() + defer sw.Set(socktest.FilterConnect, nil) + + // Avoid tracking open-close jitterbugs between netFD and + // socket that leads to confusion of information inside + // socktest.Switch. + // It may happen when the Dial call bumps against TCP + // simultaneous open. See selfConnect in tcpsock_posix.go. + defer func() { + sw.Set(socktest.FilterClose, nil) + forceCloseSockets() + }() + sw.Set(socktest.FilterClose, func(so *socktest.Status) (socktest.AfterFilter, error) { + return nil, errTimedout + }) + + for i, tt := range dialTimeoutTests { + switch runtime.GOOS { + case "plan9", "windows": + testHookDialChannel = func() { time.Sleep(tt.guard) } + if runtime.GOOS == "plan9" { + break + } + fallthrough + default: + sw.Set(socktest.FilterConnect, func(so *socktest.Status) (socktest.AfterFilter, error) { + time.Sleep(tt.guard) + return nil, errTimedout + }) + } + + ch := make(chan error) + d := Dialer{Timeout: tt.timeout} + if tt.delta != 0 { + d.Deadline = time.Now().Add(tt.delta) + } + max := time.NewTimer(tt.max) + defer max.Stop() + go func() { + // This dial never starts to send any TCP SYN + // segment because of above socket filter and + // test hook. + c, err := d.Dial("tcp", "127.0.0.1:0") + if err == nil { + err = fmt.Errorf("unexpectedly established: tcp:%s->%s", c.LocalAddr(), c.RemoteAddr()) + c.Close() + } + ch <- err + }() + + select { + case <-max.C: + t.Fatalf("#%d: Dial didn't return in an expected time", i) + case err := <-ch: + if perr := parseDialError(err); perr != nil { + t.Errorf("#%d: %v", i, perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatalf("#%d: %v", i, err) + } + } + } +} + +var acceptTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that accept deadlines in the past work, even if + // there's incoming connections available. + {-5 * time.Second, [2]error{errTimeout, errTimeout}}, + + {50 * time.Millisecond, [2]error{nil, errTimeout}}, } func TestAcceptTimeout(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - ln := newLocalListener(t).(*TCPListener) - defer ln.Close() - ln.SetDeadline(time.Now().Add(-1 * time.Second)) - if _, err := ln.Accept(); !isTimeout(err) { - t.Fatalf("Accept: expected err %v, got %v", errTimeout, err) + ln, err := newLocalListener("tcp") + if err != nil { + t.Fatal(err) } - if _, err := ln.Accept(); !isTimeout(err) { - t.Fatalf("Accept: expected err %v, got %v", errTimeout, err) + defer ln.Close() + + for i, tt := range acceptTimeoutTests { + if tt.timeout < 0 { + go func() { + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Error(err) + return + } + var b [1]byte + c.Read(b[:]) + c.Close() + }() + } + + if err := ln.(*TCPListener).SetDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("$%d: %v", i, err) + } + for j, xerr := range tt.xerrs { + for { + c, err := ln.Accept() + if xerr != nil { + if perr := parseAcceptError(err); perr != nil { + t.Errorf("#%d/%d: %v", i, j, perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatalf("#%d/%d: %v", i, j, err) + } + } + if err == nil { + c.Close() + time.Sleep(tt.timeout / 3) + continue + } + break + } + } } - ln.SetDeadline(time.Now().Add(100 * time.Millisecond)) - if _, err := ln.Accept(); !isTimeout(err) { - t.Fatalf("Accept: expected err %v, got %v", errTimeout, err) +} + +func TestAcceptTimeoutMustReturn(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) } - if _, err := ln.Accept(); !isTimeout(err) { - t.Fatalf("Accept: expected err %v, got %v", errTimeout, err) + + ln, err := newLocalListener("tcp") + if err != nil { + t.Fatal(err) } - ln.SetDeadline(noDeadline) - errc := make(chan error) + defer ln.Close() + + max := time.NewTimer(time.Second) + defer max.Stop() + ch := make(chan error) go func() { - _, err := ln.Accept() - errc <- err + if err := ln.(*TCPListener).SetDeadline(noDeadline); err != nil { + t.Error(err) + } + if err := ln.(*TCPListener).SetDeadline(time.Now().Add(10 * time.Millisecond)); err != nil { + t.Error(err) + } + c, err := ln.Accept() + if err == nil { + c.Close() + } + ch <- err }() - time.Sleep(100 * time.Millisecond) + select { - case err := <-errc: - t.Fatalf("Expected Accept() to not return, but it returned with %v\n", err) - default: - } - ln.Close() - switch nerr := <-errc; err := nerr.(type) { - case *OpError: - if err.Err != errClosing { - t.Fatalf("Accept: expected err %v, got %v", errClosing, err) + case <-max.C: + ln.Close() + <-ch // wait for tester goroutine to stop + t.Fatal("Accept didn't return in an expected time") + case err := <-ch: + if perr := parseAcceptError(err); perr != nil { + t.Error(perr) } - default: - if err != errClosing { - t.Fatalf("Accept: expected err %v, got %v", errClosing, err) + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) } } } -func TestReadTimeout(t *testing.T) { +func TestAcceptTimeoutMustNotReturn(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - ln := newLocalListener(t) - defer ln.Close() - c, err := DialTCP("tcp", nil, ln.Addr().(*TCPAddr)) + ln, err := newLocalListener("tcp") if err != nil { - t.Fatalf("Connect: %v", err) - } - defer c.Close() - c.SetDeadline(time.Now().Add(time.Hour)) - c.SetReadDeadline(time.Now().Add(-1 * time.Second)) - buf := make([]byte, 1) - if _, err = c.Read(buf); !isTimeout(err) { - t.Fatalf("Read: expected err %v, got %v", errTimeout, err) - } - if _, err = c.Read(buf); !isTimeout(err) { - t.Fatalf("Read: expected err %v, got %v", errTimeout, err) - } - c.SetDeadline(time.Now().Add(100 * time.Millisecond)) - if _, err = c.Read(buf); !isTimeout(err) { - t.Fatalf("Read: expected err %v, got %v", errTimeout, err) - } - if _, err = c.Read(buf); !isTimeout(err) { - t.Fatalf("Read: expected err %v, got %v", errTimeout, err) + t.Fatal(err) } - c.SetReadDeadline(noDeadline) - c.SetWriteDeadline(time.Now().Add(-1 * time.Second)) - errc := make(chan error) + defer ln.Close() + + max := time.NewTimer(100 * time.Millisecond) + defer max.Stop() + ch := make(chan error) go func() { - _, err := c.Read(buf) - errc <- err - }() - time.Sleep(100 * time.Millisecond) - select { - case err := <-errc: - t.Fatalf("Expected Read() to not return, but it returned with %v\n", err) - default: - } - c.Close() - switch nerr := <-errc; err := nerr.(type) { - case *OpError: - if err.Err != errClosing { - t.Fatalf("Read: expected err %v, got %v", errClosing, err) + if err := ln.(*TCPListener).SetDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) } - default: - if err == io.EOF && runtime.GOOS == "nacl" { // close enough; golang.org/issue/8044 - break + if err := ln.(*TCPListener).SetDeadline(noDeadline); err != nil { + t.Error(err) } - if err != errClosing { - t.Fatalf("Read: expected err %v, got %v", errClosing, err) + _, err := ln.Accept() + ch <- err + }() + + select { + case err := <-ch: + if perr := parseAcceptError(err); perr != nil { + t.Error(perr) } + t.Fatalf("expected Accept to not return, but it returned with %v", err) + case <-max.C: + ln.Close() + <-ch // wait for tester goroutine to stop } } -func TestWriteTimeout(t *testing.T) { +var readTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that read deadlines work, even if there's data ready + // to be read. + {-5 * time.Second, [2]error{errTimeout, errTimeout}}, + + {50 * time.Millisecond, [2]error{nil, errTimeout}}, +} + +func TestReadTimeout(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - ln := newLocalListener(t) - defer ln.Close() - c, err := DialTCP("tcp", nil, ln.Addr().(*TCPAddr)) + handler := func(ls *localServer, ln Listener) { + c, err := ln.Accept() + if err != nil { + t.Error(err) + return + } + c.Write([]byte("READ TIMEOUT TEST")) + defer c.Close() + } + ls, err := newLocalServer("tcp") + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } + + c, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String()) if err != nil { - t.Fatalf("Connect: %v", err) + t.Fatal(err) } defer c.Close() - c.SetDeadline(time.Now().Add(time.Hour)) - c.SetWriteDeadline(time.Now().Add(-1 * time.Second)) - buf := make([]byte, 4096) - writeUntilTimeout := func() { - for { - _, err := c.Write(buf) - if err != nil { - if isTimeout(err) { - return + + for i, tt := range readTimeoutTests { + if err := c.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("#%d: %v", i, err) + } + var b [1]byte + for j, xerr := range tt.xerrs { + for { + n, err := c.Read(b[:]) + if xerr != nil { + if perr := parseReadError(err); perr != nil { + t.Errorf("#%d/%d: %v", i, j, perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatalf("#%d/%d: %v", i, j, err) + } + } + if err == nil { + time.Sleep(tt.timeout / 3) + continue + } + if n != 0 { + t.Fatalf("#%d/%d: read %d; want 0", i, j, n) } - t.Fatalf("Write: expected err %v, got %v", errTimeout, err) + break } } } - writeUntilTimeout() - c.SetDeadline(time.Now().Add(10 * time.Millisecond)) - writeUntilTimeout() - writeUntilTimeout() - c.SetWriteDeadline(noDeadline) - c.SetReadDeadline(time.Now().Add(-1 * time.Second)) - errc := make(chan error) - go func() { - for { - _, err := c.Write(buf) - if err != nil { - errc <- err - } - } - }() - time.Sleep(100 * time.Millisecond) - select { - case err := <-errc: - t.Fatalf("Expected Write() to not return, but it returned with %v\n", err) - default: +} + +func TestReadTimeoutMustNotReturn(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) } - c.Close() - switch nerr := <-errc; err := nerr.(type) { - case *OpError: - if err.Err != errClosing { - t.Fatalf("Write: expected err %v, got %v", errClosing, err) - } - default: - if err != errClosing { - t.Fatalf("Write: expected err %v, got %v", errClosing, err) - } + + ln, err := newLocalListener("tcp") + if err != nil { + t.Fatal(err) } -} + defer ln.Close() -func testTimeout(t *testing.T, net, addr string, readFrom bool) { - c, err := Dial(net, addr) + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) if err != nil { - t.Errorf("Dial(%q, %q) failed: %v", net, addr, err) - return + t.Fatal(err) } defer c.Close() - what := "Read" - if readFrom { - what = "ReadFrom" - } - errc := make(chan error, 1) + max := time.NewTimer(100 * time.Millisecond) + defer max.Stop() + ch := make(chan error) go func() { - t0 := time.Now() - c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - var b [100]byte - var n int - var err error - if readFrom { - n, _, err = c.(PacketConn).ReadFrom(b[0:]) - } else { - n, err = c.Read(b[0:]) - } - t1 := time.Now() - if n != 0 || err == nil || !err.(Error).Timeout() { - errc <- fmt.Errorf("%s(%q, %q) did not return 0, timeout: %v, %v", what, net, addr, n, err) - return + if err := c.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) } - if dt := t1.Sub(t0); dt < 50*time.Millisecond || !testing.Short() && dt > 250*time.Millisecond { - errc <- fmt.Errorf("%s(%q, %q) took %s, expected 0.1s", what, net, addr, dt) - return + if err := c.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := c.SetReadDeadline(noDeadline); err != nil { + t.Error(err) } - errc <- nil + var b [1]byte + _, err := c.Read(b[:]) + ch <- err }() + select { - case err := <-errc: - if err != nil { - t.Error(err) + case err := <-ch: + if perr := parseReadError(err); perr != nil { + t.Error(perr) + } + t.Fatalf("expected Read to not return, but it returned with %v", err) + case <-max.C: + c.Close() + err := <-ch // wait for tester goroutine to stop + if perr := parseReadError(err); perr != nil { + t.Error(perr) + } + if err == io.EOF && runtime.GOOS == "nacl" { // see golang.org/issue/8044 + return + } + if nerr, ok := err.(Error); !ok || nerr.Timeout() || nerr.Temporary() { + t.Fatal(err) } - case <-time.After(1 * time.Second): - t.Errorf("%s(%q, %q) took over 1 second, expected 0.1s", what, net, addr) } } -func TestTimeoutUDP(t *testing.T) { +var readFromTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that read deadlines work, even if there's data ready + // to be read. + {-5 * time.Second, [2]error{errTimeout, errTimeout}}, + + {50 * time.Millisecond, [2]error{nil, errTimeout}}, +} + +func TestReadFromTimeout(t *testing.T) { switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) // see golang.org/issue/8916 } - // set up a listener that won't talk back - listening := make(chan string) - done := make(chan int) - go runDatagramPacketConnServer(t, "udp", "127.0.0.1:0", listening, done) - addr := <-listening + ch := make(chan Addr) + defer close(ch) + handler := func(ls *localPacketServer, c PacketConn) { + if dst, ok := <-ch; ok { + c.WriteTo([]byte("READFROM TIMEOUT TEST"), dst) + } + } + ls, err := newLocalPacketServer("udp") + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } - testTimeout(t, "udp", addr, false) - testTimeout(t, "udp", addr, true) - <-done -} + host, _, err := SplitHostPort(ls.PacketConn.LocalAddr().String()) + if err != nil { + t.Fatal(err) + } + c, err := ListenPacket(ls.PacketConn.LocalAddr().Network(), JoinHostPort(host, "0")) + if err != nil { + t.Fatal(err) + } + defer c.Close() + ch <- c.LocalAddr() -func TestTimeoutTCP(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + for i, tt := range readFromTimeoutTests { + if err := c.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("#%d: %v", i, err) + } + var b [1]byte + for j, xerr := range tt.xerrs { + for { + n, _, err := c.ReadFrom(b[:]) + if xerr != nil { + if perr := parseReadError(err); perr != nil { + t.Errorf("#%d/%d: %v", i, j, perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatalf("#%d/%d: %v", i, j, err) + } + } + if err == nil { + time.Sleep(tt.timeout / 3) + continue + } + if n != 0 { + t.Fatalf("#%d/%d: read %d; want 0", i, j, n) + } + break + } + } } +} - // set up a listener that won't talk back - listening := make(chan string) - done := make(chan int) - go runStreamConnServer(t, "tcp", "127.0.0.1:0", listening, done) - addr := <-listening +var writeTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that write deadlines work, even if there's buffer + // space available to write. + {-5 * time.Second, [2]error{errTimeout, errTimeout}}, - testTimeout(t, "tcp", addr, false) - <-done + {10 * time.Millisecond, [2]error{nil, errTimeout}}, } -func TestDeadlineReset(t *testing.T) { +func TestWriteTimeout(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - ln, err := Listen("tcp", "127.0.0.1:0") + + ln, err := newLocalListener("tcp") if err != nil { t.Fatal(err) } defer ln.Close() - tl := ln.(*TCPListener) - tl.SetDeadline(time.Now().Add(1 * time.Minute)) - tl.SetDeadline(noDeadline) // reset it - errc := make(chan error, 1) - go func() { - _, err := ln.Accept() - errc <- err - }() - select { - case <-time.After(50 * time.Millisecond): - // Pass. - case err := <-errc: - // Accept should never return; we never - // connected to it. - t.Errorf("unexpected return from Accept; err=%v", err) + + for i, tt := range writeTimeoutTests { + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + if err := c.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("#%d: %v", i, err) + } + for j, xerr := range tt.xerrs { + for { + n, err := c.Write([]byte("WRITE TIMEOUT TEST")) + if xerr != nil { + if perr := parseWriteError(err); perr != nil { + t.Errorf("#%d/%d: %v", i, j, perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatalf("#%d/%d: %v", i, j, err) + } + } + if err == nil { + time.Sleep(tt.timeout / 3) + continue + } + if n != 0 { + t.Fatalf("#%d/%d: wrote %d; want 0", i, j, n) + } + break + } + } } } -func TestTimeoutAccept(t *testing.T) { +func TestWriteTimeoutMustNotReturn(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - ln, err := Listen("tcp", "127.0.0.1:0") + + ln, err := newLocalListener("tcp") 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) + + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + max := time.NewTimer(100 * time.Millisecond) + defer max.Stop() + ch := make(chan error) go func() { - _, err := ln.Accept() - errc <- err + if err := c.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := c.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := c.SetWriteDeadline(noDeadline); err != nil { + t.Error(err) + } + var b [1]byte + for { + if _, err := c.Write(b[:]); err != nil { + ch <- err + break + } + } }() + 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. + case err := <-ch: + if perr := parseWriteError(err); perr != nil { + t.Error(perr) + } + t.Fatalf("expected Write to not return, but it returned with %v", err) + case <-max.C: + c.Close() + err := <-ch // wait for tester goroutine to stop + if perr := parseWriteError(err); perr != nil { + t.Error(perr) + } + if nerr, ok := err.(Error); !ok || nerr.Timeout() || nerr.Temporary() { + t.Fatal(err) + } } } -func TestReadWriteDeadline(t *testing.T) { +var writeToTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that write deadlines work, even if there's buffer + // space available to write. + {-5 * time.Second, [2]error{errTimeout, errTimeout}}, + + {10 * time.Millisecond, [2]error{nil, errTimeout}}, +} + +func TestWriteToTimeout(t *testing.T) { switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) } - 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) - } + c1, err := newLocalPacketListener("udp") + if err != nil { + t.Fatal(err) } + defer c1.Close() - ln, err := Listen("tcp", "127.0.0.1:0") + host, _, err := SplitHostPort(c1.LocalAddr().String()) if err != nil { - t.Fatalf("ListenTCP on :0: %v", err) + t.Fatal(err) } - defer ln.Close() - lnquit := make(chan bool) - - go func() { - c, err := ln.Accept() + for i, tt := range writeToTimeoutTests { + c2, err := ListenPacket(c1.LocalAddr().Network(), JoinHostPort(host, "0")) if err != nil { - t.Errorf("Accept: %v", err) - return + t.Fatal(err) } - defer c.Close() - lnquit <- true - }() + defer c2.Close() + + if err := c2.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("#%d: %v", i, err) + } + for j, xerr := range tt.xerrs { + for { + n, err := c2.WriteTo([]byte("WRITETO TIMEOUT TEST"), c1.LocalAddr()) + if xerr != nil { + if perr := parseWriteError(err); perr != nil { + t.Errorf("#%d/%d: %v", i, j, perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatalf("#%d/%d: %v", i, j, err) + } + } + if err == nil { + time.Sleep(tt.timeout / 3) + continue + } + if n != 0 { + t.Fatalf("#%d/%d: wrote %d; want 0", i, j, n) + } + break + } + } + } +} + +func TestReadTimeoutFluctuation(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } - c, err := Dial("tcp", ln.Addr().String()) + ln, err := newLocalListener("tcp") if err != nil { - t.Fatalf("Dial: %v", err) + t.Fatal(err) + } + defer ln.Close() + + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Fatal(err) } defer c.Close() - start := time.Now() - err = c.SetReadDeadline(start.Add(readTimeout)) + max := time.NewTimer(time.Second) + defer max.Stop() + ch := make(chan error) + go timeoutReceiver(c, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch) + + select { + case <-max.C: + t.Fatal("Read took over 1s; expected 0.1s") + case err := <-ch: + if perr := parseReadError(err); perr != nil { + t.Error(perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) + } + } +} + +func TestReadFromTimeoutFluctuation(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + + c1, err := newLocalPacketListener("udp") if err != nil { - t.Fatalf("SetReadDeadline: %v", err) + t.Fatal(err) } - err = c.SetWriteDeadline(start.Add(writeTimeout)) + defer c1.Close() + + c2, err := Dial(c1.LocalAddr().Network(), c1.LocalAddr().String()) if err != nil { - t.Fatalf("SetWriteDeadline: %v", err) + t.Fatal(err) } + defer c2.Close() - quit := make(chan bool) + max := time.NewTimer(time.Second) + defer max.Stop() + ch := make(chan error) + go timeoutPacketReceiver(c2.(PacketConn), 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch) - go func() { - var buf [10]byte - _, err := c.Read(buf[:]) - if err == nil { - t.Errorf("Read should not succeed") + select { + case <-max.C: + t.Fatal("ReadFrom took over 1s; expected 0.1s") + case err := <-ch: + if perr := parseReadError(err); perr != nil { + t.Error(perr) } - checkTimeout("Read", start, readTimeout) - quit <- true - }() - - go func() { - var buf [10000]byte - for { - _, err := c.Write(buf[:]) - if err != nil { - break - } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) } - checkTimeout("Write", start, writeTimeout) - quit <- true - }() - - <-quit - <-quit - <-lnquit + } } -type neverEnding byte +func TestWriteTimeoutFluctuation(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } -func (b neverEnding) Read(p []byte) (n int, err error) { - for i := range p { - p[i] = byte(b) + ln, err := newLocalListener("tcp") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + d := time.Second + if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { + d = 3 * time.Second // see golang.org/issue/10775 + } + max := time.NewTimer(d) + defer max.Stop() + ch := make(chan error) + go timeoutTransmitter(c, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch) + + select { + case <-max.C: + t.Fatalf("Write took over %v; expected 0.1s", d) + case err := <-ch: + if perr := parseWriteError(err); perr != nil { + t.Error(perr) + } + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) + } } - return len(p), nil } func TestVariousDeadlines1Proc(t *testing.T) { @@ -420,36 +739,57 @@ func TestVariousDeadlines4Proc(t *testing.T) { testVariousDeadlines(t, 4) } +type neverEnding byte + +func (b neverEnding) Read(p []byte) (int, error) { + for i := range p { + p[i] = byte(b) + } + return len(p), nil +} + func testVariousDeadlines(t *testing.T, maxProcs int) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) - ln := newLocalListener(t) - defer ln.Close() - acceptc := make(chan error, 1) - // The server, with no timeouts of its own, sending bytes to clients - // as fast as it can. - servec := make(chan copyRes) - go func() { + type result struct { + n int64 + err error + d time.Duration + } + + ch := make(chan error, 1) + pasvch := make(chan result) + handler := func(ls *localServer, ln Listener) { for { c, err := ln.Accept() if err != nil { - acceptc <- err + ch <- err return } + // The server, with no timeouts of its own, + // sending bytes to clients as fast as it can. go func() { t0 := time.Now() n, err := io.Copy(c, neverEnding('a')) - d := time.Since(t0) + dt := time.Since(t0) c.Close() - servec <- copyRes{n, err, d} + pasvch <- result{n, err, dt} }() } - }() + } + ls, err := newLocalServer("tcp") + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } for _, timeout := range []time.Duration{ 1 * time.Nanosecond, @@ -483,236 +823,133 @@ func testVariousDeadlines(t *testing.T, maxProcs int) { name := fmt.Sprintf("%v run %d/%d", timeout, run+1, numRuns) t.Log(name) - c, err := Dial("tcp", ln.Addr().String()) + c, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String()) if err != nil { - t.Fatalf("Dial: %v", err) + t.Fatal(err) } - clientc := make(chan copyRes) + + tooLong := 5 * time.Second + max := time.NewTimer(tooLong) + defer max.Stop() + actvch := make(chan result) go func() { t0 := time.Now() - c.SetDeadline(t0.Add(timeout)) + if err := c.SetDeadline(t0.Add(timeout)); err != nil { + t.Error(err) + } n, err := io.Copy(ioutil.Discard, c) - d := time.Since(t0) + dt := time.Since(t0) c.Close() - clientc <- copyRes{n, err, d} + actvch <- result{n, err, dt} }() - tooLong := 5 * time.Second select { - case res := <-clientc: - if isTimeout(res.err) { + case res := <-actvch: + if nerr, ok := res.err.(Error); ok && nerr.Timeout() { t.Logf("for %v, good client timeout after %v, reading %d bytes", name, res.d, res.n) } else { - t.Fatalf("for %v: client Copy = %d, %v (want timeout)", name, res.n, res.err) + t.Fatalf("for %v, client Copy = %d, %v; want timeout", name, res.n, res.err) } - case <-time.After(tooLong): - t.Fatalf("for %v: timeout (%v) waiting for client to timeout (%v) reading", name, tooLong, timeout) + case <-max.C: + t.Fatalf("for %v, timeout (%v) waiting for client to timeout (%v) reading", name, tooLong, timeout) } select { - case res := <-servec: - t.Logf("for %v: server in %v wrote %d, %v", name, res.d, res.n, res.err) - case err := <-acceptc: - t.Fatalf("for %v: server Accept = %v", name, err) - case <-time.After(tooLong): + case res := <-pasvch: + t.Logf("for %v, server in %v wrote %d: %v", name, res.d, res.n, res.err) + case err := <-ch: + t.Fatalf("for %v, Accept = %v", name, err) + case <-max.C: t.Fatalf("for %v, timeout waiting for server to finish writing", name) } } } } -// TestReadDeadlineDataAvailable tests that read deadlines work, even -// if there's data ready to be read. -func TestReadDeadlineDataAvailable(t *testing.T) { +// TestReadWriteProlongedTimeout tests concurrent deadline +// modification. Known to cause data races in the past. +func TestReadWriteProlongedTimeout(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - ln := newLocalListener(t) - defer ln.Close() - - servec := make(chan copyRes) - const msg = "data client shouldn't read, even though it'll be waiting" - go func() { + handler := func(ls *localServer, ln Listener) { c, err := ln.Accept() if err != nil { - t.Errorf("Accept: %v", err) - return - } - defer c.Close() - n, err := c.Write([]byte(msg)) - servec <- copyRes{n: int64(n), err: err} - }() - - c, err := Dial("tcp", ln.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer c.Close() - if res := <-servec; res.err != nil || res.n != int64(len(msg)) { - t.Fatalf("unexpected server Write: n=%d, err=%v; want n=%d, err=nil", res.n, res.err, len(msg)) - } - c.SetReadDeadline(time.Now().Add(-5 * time.Second)) // in the psat. - buf := make([]byte, len(msg)/2) - n, err := c.Read(buf) - if n > 0 || !isTimeout(err) { - t.Fatalf("client read = %d (%q) err=%v; want 0, timeout", n, buf[:n], err) - } -} - -// TestWriteDeadlineBufferAvailable tests that write deadlines work, even -// if there's buffer space available to write. -func TestWriteDeadlineBufferAvailable(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - ln := newLocalListener(t) - defer ln.Close() - - servec := make(chan copyRes) - go func() { - c, err := ln.Accept() - if err != nil { - t.Errorf("Accept: %v", err) - return - } - defer c.Close() - c.SetWriteDeadline(time.Now().Add(-5 * time.Second)) // in the past - n, err := c.Write([]byte{'x'}) - servec <- copyRes{n: int64(n), err: err} - }() - - c, err := Dial("tcp", ln.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer c.Close() - res := <-servec - if res.n != 0 { - t.Errorf("Write = %d; want 0", res.n) - } - if !isTimeout(res.err) { - t.Errorf("Write error = %v; want timeout", res.err) - } -} - -// TestAcceptDeadlineConnectionAvailable tests that accept deadlines work, even -// if there's incoming connections available. -func TestAcceptDeadlineConnectionAvailable(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - ln := newLocalListener(t).(*TCPListener) - defer ln.Close() - - go func() { - c, err := Dial("tcp", ln.Addr().String()) - if err != nil { - t.Errorf("Dial: %v", err) + t.Error(err) return } defer c.Close() - var buf [1]byte - c.Read(buf[:]) // block until the connection or listener is closed - }() - time.Sleep(10 * time.Millisecond) - ln.SetDeadline(time.Now().Add(-5 * time.Second)) // in the past - c, err := ln.Accept() - if err == nil { - defer c.Close() - } - if !isTimeout(err) { - t.Fatalf("Accept: got %v; want timeout", err) - } -} - -// TestConnectDeadlineInThePast tests that connect deadlines work, even -// if the connection can be established w/o blocking. -func TestConnectDeadlineInThePast(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - ln := newLocalListener(t).(*TCPListener) - defer ln.Close() - - go func() { - c, err := ln.Accept() - if err == nil { - defer c.Close() - } - }() - time.Sleep(10 * time.Millisecond) - c, err := DialTimeout("tcp", ln.Addr().String(), -5*time.Second) // in the past - if err == nil { - defer c.Close() - } - if !isTimeout(err) { - t.Fatalf("DialTimeout: got %v; want timeout", err) - } -} -// TestProlongTimeout tests concurrent deadline modification. -// Known to cause data races in the past. -func TestProlongTimeout(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - ln := newLocalListener(t) - defer ln.Close() - connected := make(chan bool) - go func() { - s, err := ln.Accept() - connected <- true - if err != nil { - t.Errorf("ln.Accept: %v", err) - return - } - defer s.Close() - s.SetDeadline(time.Now().Add(time.Hour)) + var wg sync.WaitGroup + wg.Add(2) go func() { - var buf [4096]byte + defer wg.Done() + var b [1]byte for { - _, err := s.Write(buf[:]) - if err != nil { - break + if err := c.SetReadDeadline(time.Now().Add(time.Hour)); err != nil { + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + t.Error(err) + return + } + if _, err := c.Read(b[:]); err != nil { + if perr := parseReadError(err); perr != nil { + t.Error(perr) + } + return } - s.SetDeadline(time.Now().Add(time.Hour)) } }() - buf := make([]byte, 1) - for { - _, err := s.Read(buf) - if err != nil { - break + go func() { + defer wg.Done() + var b [1]byte + for { + if err := c.SetWriteDeadline(time.Now().Add(time.Hour)); err != nil { + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + t.Error(err) + return + } + if _, err := c.Write(b[:]); err != nil { + if perr := parseWriteError(err); perr != nil { + t.Error(perr) + } + return + } } - s.SetDeadline(time.Now().Add(time.Hour)) - } - }() - c, err := Dial("tcp", ln.Addr().String()) + }() + wg.Wait() + } + ls, err := newLocalServer("tcp") + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } + + c, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String()) if err != nil { - t.Fatalf("DialTCP: %v", err) + t.Fatal(err) } defer c.Close() - <-connected - for i := 0; i < 1024; i++ { - var buf [1]byte - c.Write(buf[:]) + + var b [1]byte + for i := 0; i < 1000; i++ { + c.Write(b[:]) + c.Read(b[:]) } } -func TestDeadlineRace(t *testing.T) { +func TestReadWriteDeadlineRace(t *testing.T) { switch runtime.GOOS { case "nacl", "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } N := 1000 @@ -720,28 +957,54 @@ func TestDeadlineRace(t *testing.T) { N = 50 } defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) - ln := newLocalListener(t) + + ln, err := newLocalListener("tcp") + if err != nil { + t.Fatal(err) + } defer ln.Close() - c, err := Dial("tcp", ln.Addr().String()) + + c, err := Dial(ln.Addr().Network(), ln.Addr().String()) if err != nil { - t.Fatalf("Dial: %v", err) + t.Fatal(err) } defer c.Close() - done := make(chan bool) + + var wg sync.WaitGroup + wg.Add(3) go func() { - t := time.NewTicker(2 * time.Microsecond).C + defer wg.Done() + tic := time.NewTicker(2 * time.Microsecond) + defer tic.Stop() for i := 0; i < N; i++ { - if err := c.SetDeadline(time.Now().Add(2 * time.Microsecond)); err != nil { + if err := c.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil { + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } + break + } + if err := c.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil { + if perr := parseCommonError(err); perr != nil { + t.Error(perr) + } break } - <-t + <-tic.C } - done <- true }() - var buf [1]byte - for i := 0; i < N; i++ { - c.Read(buf[:]) // ignore possible timeout errors - } - c.Close() - <-done + go func() { + defer wg.Done() + var b [1]byte + for i := 0; i < N; i++ { + c.Read(b[:]) // ignore possible timeout errors + } + }() + go func() { + defer wg.Done() + var b [1]byte + for i := 0; i < N; i++ { + c.Write(b[:]) // ignore possible timeout errors + } + }() + wg.Wait() // wait for tester goroutine to stop } diff --git a/libgo/go/net/udp_test.go b/libgo/go/net/udp_test.go index 125bbca6c40..b25f96a3fd3 100644 --- a/libgo/go/net/udp_test.go +++ b/libgo/go/net/udp_test.go @@ -7,149 +7,164 @@ package net import ( "reflect" "runtime" - "strings" "testing" "time" ) -func TestResolveUDPAddr(t *testing.T) { - for _, tt := range resolveTCPAddrTests { - net := strings.Replace(tt.net, "tcp", "udp", -1) - addr, err := ResolveUDPAddr(net, tt.litAddrOrName) - if err != tt.err { - t.Fatalf("ResolveUDPAddr(%q, %q) failed: %v", net, tt.litAddrOrName, err) - } - if !reflect.DeepEqual(addr, (*UDPAddr)(tt.addr)) { - t.Fatalf("ResolveUDPAddr(%q, %q) = %#v, want %#v", net, tt.litAddrOrName, addr, tt.addr) - } - if err == nil { - str := addr.String() - addr1, err := ResolveUDPAddr(net, str) - if err != nil { - t.Fatalf("ResolveUDPAddr(%q, %q) [from %q]: %v", net, str, tt.litAddrOrName, err) - } - if !reflect.DeepEqual(addr1, addr) { - t.Fatalf("ResolveUDPAddr(%q, %q) [from %q] = %#v, want %#v", net, str, tt.litAddrOrName, addr1, addr) - } - } - } +type resolveUDPAddrTest struct { + network string + litAddrOrName string + addr *UDPAddr + err error } -func TestReadFromUDP(t *testing.T) { - switch runtime.GOOS { - case "nacl", "plan9": - t.Skipf("skipping test on %q, see issue 8916", runtime.GOOS) - } +var resolveUDPAddrTests = []resolveUDPAddrTest{ + {"udp", "127.0.0.1:0", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, + {"udp4", "127.0.0.1:65535", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 65535}, nil}, - ra, err := ResolveUDPAddr("udp", "127.0.0.1:7") - if err != nil { - t.Fatal(err) - } + {"udp", "[::1]:0", &UDPAddr{IP: ParseIP("::1"), Port: 0}, nil}, + {"udp6", "[::1]:65535", &UDPAddr{IP: ParseIP("::1"), Port: 65535}, nil}, - la, err := ResolveUDPAddr("udp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } + {"udp", "[::1%en0]:1", &UDPAddr{IP: ParseIP("::1"), Port: 1, Zone: "en0"}, nil}, + {"udp6", "[::1%911]:2", &UDPAddr{IP: ParseIP("::1"), Port: 2, Zone: "911"}, nil}, - c, err := ListenUDP("udp", la) - if err != nil { - t.Fatal(err) - } - defer c.Close() + {"", "127.0.0.1:0", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, // Go 1.0 behavior + {"", "[::1]:0", &UDPAddr{IP: ParseIP("::1"), Port: 0}, nil}, // Go 1.0 behavior - _, err = c.WriteToUDP([]byte("a"), ra) - if err != nil { - t.Fatal(err) - } + {"udp", ":12345", &UDPAddr{Port: 12345}, nil}, - err = c.SetDeadline(time.Now().Add(100 * time.Millisecond)) - if err != nil { - t.Fatal(err) - } - b := make([]byte, 1) - _, _, err = c.ReadFromUDP(b) - if err == nil { - t.Fatal("ReadFromUDP should fail") - } else if !isTimeout(err) { - t.Fatal(err) + {"http", "127.0.0.1:0", nil, UnknownNetworkError("http")}, +} + +func TestResolveUDPAddr(t *testing.T) { + origTestHookLookupIP := testHookLookupIP + defer func() { testHookLookupIP = origTestHookLookupIP }() + testHookLookupIP = lookupLocalhost + + for i, tt := range resolveUDPAddrTests { + addr, err := ResolveUDPAddr(tt.network, tt.litAddrOrName) + if err != tt.err { + t.Errorf("#%d: %v", i, err) + } else if !reflect.DeepEqual(addr, tt.addr) { + t.Errorf("#%d: got %#v; want %#v", i, addr, tt.addr) + } + if err != nil { + continue + } + rtaddr, err := ResolveUDPAddr(addr.Network(), addr.String()) + if err != nil { + t.Errorf("#%d: %v", i, err) + } else if !reflect.DeepEqual(rtaddr, addr) { + t.Errorf("#%d: got %#v; want %#v", i, rtaddr, addr) + } } } func TestWriteToUDP(t *testing.T) { switch runtime.GOOS { case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) + t.Skipf("not supported on %s", runtime.GOOS) } - l, err := ListenPacket("udp", "127.0.0.1:0") + c, err := ListenPacket("udp", "127.0.0.1:0") if err != nil { - t.Fatalf("Listen failed: %v", err) + t.Fatal(err) } - defer l.Close() + defer c.Close() - testWriteToConn(t, l.LocalAddr().String()) - testWriteToPacketConn(t, l.LocalAddr().String()) + testWriteToConn(t, c.LocalAddr().String()) + testWriteToPacketConn(t, c.LocalAddr().String()) } func testWriteToConn(t *testing.T, raddr string) { c, err := Dial("udp", raddr) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c.Close() ra, err := ResolveUDPAddr("udp", raddr) if err != nil { - t.Fatalf("ResolveUDPAddr failed: %v", err) + t.Fatal(err) } - _, err = c.(*UDPConn).WriteToUDP([]byte("Connection-oriented mode socket"), ra) + b := []byte("CONNECTED-MODE SOCKET") + _, err = c.(*UDPConn).WriteToUDP(b, ra) if err == nil { - t.Fatal("WriteToUDP should fail") + t.Fatal("should fail") } if err != nil && err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteToUDP should fail as ErrWriteToConnected: %v", err) + t.Fatalf("should fail as ErrWriteToConnected: %v", err) } - - _, err = c.(*UDPConn).WriteTo([]byte("Connection-oriented mode socket"), ra) + _, err = c.(*UDPConn).WriteTo(b, ra) if err == nil { - t.Fatal("WriteTo should fail") + t.Fatal("should fail") } if err != nil && err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteTo should fail as ErrWriteToConnected: %v", err) + t.Fatalf("should fail as ErrWriteToConnected: %v", err) } - - _, err = c.Write([]byte("Connection-oriented mode socket")) + _, err = c.Write(b) if err != nil { - t.Fatalf("Write failed: %v", err) + t.Fatal(err) + } + _, _, err = c.(*UDPConn).WriteMsgUDP(b, nil, ra) + if err == nil { + t.Fatal("should fail") + } + if err != nil && err.(*OpError).Err != ErrWriteToConnected { + t.Fatalf("should fail as ErrWriteToConnected: %v", err) + } + _, _, err = c.(*UDPConn).WriteMsgUDP(b, nil, nil) + switch runtime.GOOS { + case "nacl", "windows": // see golang.org/issue/9252 + t.Skipf("not implemented yet on %s", runtime.GOOS) + default: + if err != nil { + t.Fatal(err) + } } } func testWriteToPacketConn(t *testing.T, raddr string) { c, err := ListenPacket("udp", "127.0.0.1:0") if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } defer c.Close() ra, err := ResolveUDPAddr("udp", raddr) if err != nil { - t.Fatalf("ResolveUDPAddr failed: %v", err) + t.Fatal(err) } - _, err = c.(*UDPConn).WriteToUDP([]byte("Connection-less mode socket"), ra) + b := []byte("UNCONNECTED-MODE SOCKET") + _, err = c.(*UDPConn).WriteToUDP(b, ra) if err != nil { - t.Fatalf("WriteToUDP failed: %v", err) + t.Fatal(err) } - - _, err = c.WriteTo([]byte("Connection-less mode socket"), ra) + _, err = c.WriteTo(b, ra) if err != nil { - t.Fatalf("WriteTo failed: %v", err) + t.Fatal(err) } - - _, err = c.(*UDPConn).Write([]byte("Connection-less mode socket")) + _, err = c.(*UDPConn).Write(b) + if err == nil { + t.Fatal("should fail") + } + _, _, err = c.(*UDPConn).WriteMsgUDP(b, nil, nil) if err == nil { - t.Fatal("Write should fail") + t.Fatal("should fail") + } + if err != nil && err.(*OpError).Err != errMissingAddress { + t.Fatalf("should fail as errMissingAddress: %v", err) + } + _, _, err = c.(*UDPConn).WriteMsgUDP(b, nil, ra) + switch runtime.GOOS { + case "nacl", "windows": // see golang.org/issue/9252 + t.Skipf("not implemented yet on %s", runtime.GOOS) + default: + if err != nil { + t.Fatal(err) + } } } @@ -164,13 +179,13 @@ var udpConnLocalNameTests = []struct { func TestUDPConnLocalName(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } for _, tt := range udpConnLocalNameTests { c, err := ListenUDP(tt.net, tt.laddr) if err != nil { - t.Fatalf("ListenUDP failed: %v", err) + t.Fatal(err) } defer c.Close() la := c.LocalAddr() @@ -184,7 +199,7 @@ func TestUDPConnLocalAndRemoteNames(t *testing.T) { for _, laddr := range []string{"", "127.0.0.1:0"} { c1, err := ListenPacket("udp", "127.0.0.1:0") if err != nil { - t.Fatalf("ListenUDP failed: %v", err) + t.Fatal(err) } defer c1.Close() @@ -192,12 +207,12 @@ func TestUDPConnLocalAndRemoteNames(t *testing.T) { if laddr != "" { var err error if la, err = ResolveUDPAddr("udp", laddr); err != nil { - t.Fatalf("ResolveUDPAddr failed: %v", err) + t.Fatal(err) } } c2, err := DialUDP("udp", la, c1.LocalAddr().(*UDPAddr)) if err != nil { - t.Fatalf("DialUDP failed: %v", err) + t.Fatal(err) } defer c2.Close() @@ -220,60 +235,37 @@ func TestUDPConnLocalAndRemoteNames(t *testing.T) { func TestIPv6LinkLocalUnicastUDP(t *testing.T) { if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") + t.Skip("avoid external network") } if !supportsIPv6 { - t.Skip("ipv6 is not supported") - } - ifi := loopbackInterface() - if ifi == nil { - t.Skip("loopback interface not found") - } - laddr := ipv6LinkLocalUnicastAddr(ifi) - if laddr == "" { - t.Skip("ipv6 unicast address on loopback not found") + t.Skip("IPv6 is not supported") } - type test struct { - net, addr string - nameLookup bool - } - var tests = []test{ - {"udp", "[" + laddr + "%" + ifi.Name + "]:0", false}, - {"udp6", "[" + laddr + "%" + ifi.Name + "]:0", false}, - } - // The first udp test fails on DragonFly - see issue 7473. - if runtime.GOOS == "dragonfly" { - tests = tests[1:] - } - switch runtime.GOOS { - case "darwin", "dragonfly", "freebsd", "openbsd", "netbsd": - tests = append(tests, []test{ - {"udp", "[localhost%" + ifi.Name + "]:0", true}, - {"udp6", "[localhost%" + ifi.Name + "]:0", true}, - }...) - case "linux": - tests = append(tests, []test{ - {"udp", "[ip6-localhost%" + ifi.Name + "]:0", true}, - {"udp6", "[ip6-localhost%" + ifi.Name + "]:0", true}, - }...) - } - for _, tt := range tests { - c1, err := ListenPacket(tt.net, tt.addr) + for i, tt := range ipv6LinkLocalUnicastUDPTests { + c1, err := ListenPacket(tt.network, tt.address) if err != nil { // It might return "LookupHost returned no // suitable address" error on some platforms. - t.Logf("ListenPacket failed: %v", err) + t.Log(err) continue } - defer c1.Close() + ls, err := (&packetListener{PacketConn: c1}).newLocalServer() + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + ch := make(chan error, 1) + handler := func(ls *localPacketServer, c PacketConn) { packetTransponder(c, ch) } + if err := ls.buildup(handler); err != nil { + t.Fatal(err) + } if la, ok := c1.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" { t.Fatalf("got %v; expected a proper address with zone identifier", la) } - c2, err := Dial(tt.net, c1.LocalAddr().String()) + c2, err := Dial(tt.network, ls.PacketConn.LocalAddr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c2.Close() if la, ok := c2.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" { @@ -284,14 +276,88 @@ func TestIPv6LinkLocalUnicastUDP(t *testing.T) { } if _, err := c2.Write([]byte("UDP OVER IPV6 LINKLOCAL TEST")); err != nil { - t.Fatalf("Conn.Write failed: %v", err) + t.Fatal(err) } b := make([]byte, 32) - if _, from, err := c1.ReadFrom(b); err != nil { - t.Fatalf("PacketConn.ReadFrom failed: %v", err) + if _, err := c2.Read(b); err != nil { + t.Fatal(err) + } + + for err := range ch { + t.Errorf("#%d: %v", i, err) + } + } +} + +func TestUDPZeroBytePayload(t *testing.T) { + switch runtime.GOOS { + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + + c, err := newLocalPacketListener("udp") + if err != nil { + t.Fatal(err) + } + defer c.Close() + + for _, genericRead := range []bool{false, true} { + n, err := c.WriteTo(nil, c.LocalAddr()) + if err != nil { + t.Fatal(err) + } + if n != 0 { + t.Errorf("got %d; want 0", n) + } + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + var b [1]byte + if genericRead { + _, err = c.(Conn).Read(b[:]) } else { - if ra, ok := from.(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" { - t.Fatalf("got %v; expected a proper address with zone identifier", ra) + _, _, err = c.ReadFrom(b[:]) + } + switch err { + case nil: // ReadFrom succeeds + default: // Read may timeout, it depends on the platform + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) + } + } + } +} + +func TestUDPZeroByteBuffer(t *testing.T) { + switch runtime.GOOS { + case "nacl", "plan9": + t.Skipf("not supported on %s", runtime.GOOS) + } + + c, err := newLocalPacketListener("udp") + if err != nil { + t.Fatal(err) + } + defer c.Close() + + b := []byte("UDP ZERO BYTE BUFFER TEST") + for _, genericRead := range []bool{false, true} { + n, err := c.WriteTo(b, c.LocalAddr()) + if err != nil { + t.Fatal(err) + } + if n != len(b) { + t.Errorf("got %d; want %d", n, len(b)) + } + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + if genericRead { + _, err = c.(Conn).Read(nil) + } else { + _, _, err = c.ReadFrom(nil) + } + switch err { + case nil: // ReadFrom succeeds + default: // Read may timeout, it depends on the platform + if nerr, ok := err.(Error); (!ok || !nerr.Timeout()) && runtime.GOOS != "windows" { // Windows retruns WSAEMSGSIZ + t.Fatal(err) } } } diff --git a/libgo/go/net/udpsock.go b/libgo/go/net/udpsock.go index 4c99ae4af68..9292133aeb8 100644 --- a/libgo/go/net/udpsock.go +++ b/libgo/go/net/udpsock.go @@ -25,7 +25,14 @@ func (a *UDPAddr) String() string { return JoinHostPort(ip, itoa(a.Port)) } -func (a *UDPAddr) toAddr() Addr { +func (a *UDPAddr) isWildcard() bool { + if a == nil || a.IP == nil { + return true + } + return a.IP.IsUnspecified() +} + +func (a *UDPAddr) opAddr() Addr { if a == nil { return nil } @@ -46,9 +53,9 @@ func ResolveUDPAddr(net, addr string) (*UDPAddr, error) { default: return nil, UnknownNetworkError(net) } - a, err := resolveInternetAddr(net, addr, noDeadline) + addrs, err := internetAddrList(net, addr, noDeadline) if err != nil { return nil, err } - return a.toAddr().(*UDPAddr), nil + return addrs.first(isIPv4).(*UDPAddr), nil } diff --git a/libgo/go/net/udpsock_plan9.go b/libgo/go/net/udpsock_plan9.go index 510ac5e4aaa..1ba57a227e9 100644 --- a/libgo/go/net/udpsock_plan9.go +++ b/libgo/go/net/udpsock_plan9.go @@ -33,10 +33,10 @@ func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { buf := make([]byte, udpHeaderSize+len(b)) m, err := c.fd.data.Read(buf) if err != nil { - return + return 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} } if m < udpHeaderSize { - return 0, nil, errors.New("short read reading UDP header") + return 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: errors.New("short read reading UDP header")} } buf = buf[:m] @@ -59,7 +59,7 @@ func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) { // flags that were set on the packet and the source address of the // packet. func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) { - return 0, 0, 0, nil, syscall.EPLAN9 + return 0, 0, 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // WriteToUDP writes a UDP packet to addr via c, copying the payload @@ -74,7 +74,7 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) { return 0, syscall.EINVAL } if addr == nil { - return 0, &OpError{Op: "write", Net: c.fd.dir, Addr: nil, Err: errMissingAddress} + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: nil, Err: errMissingAddress} } h := new(udpHeader) h.raddr = addr.IP.To16() @@ -86,7 +86,10 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) { buf := make([]byte, udpHeaderSize+len(b)) i := copy(buf, h.Bytes()) copy(buf[i:], b) - return c.fd.data.Write(buf) + if _, err := c.fd.data.Write(buf); err != nil { + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + return len(b), nil } // WriteTo implements the PacketConn WriteTo method. @@ -96,16 +99,18 @@ func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) { } a, ok := addr.(*UDPAddr) if !ok { - return 0, &OpError{"write", c.fd.dir, addr, syscall.EINVAL} + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL} } return c.WriteToUDP(b, a) } -// WriteMsgUDP writes a packet to addr via c, copying the payload from -// b and the associated out-of-band data from oob. It returns the -// number of payload and out-of-band bytes written. +// WriteMsgUDP writes a packet to addr via c if c isn't connected, or +// to c's remote destination address if c is connected (in which case +// addr must be nil). The payload is copied from b and the associated +// out-of-band data is copied from oob. It returns the number of +// payload and out-of-band bytes written. func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) { - return 0, 0, syscall.EPLAN9 + return 0, 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EPLAN9} } // DialUDP connects to the remote address raddr on the network net, @@ -122,10 +127,10 @@ func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, e switch net { case "udp", "udp4", "udp6": default: - return nil, UnknownNetworkError(net) + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(net)} } if raddr == nil { - return nil, &OpError{"dial", net, nil, errMissingAddress} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress} } fd, err := dialPlan9(net, laddr, raddr) if err != nil { @@ -173,7 +178,7 @@ func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) { switch net { case "udp", "udp4", "udp6": default: - return nil, UnknownNetworkError(net) + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(net)} } if laddr == nil { laddr = &UDPAddr{} @@ -184,20 +189,27 @@ func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) { } _, err = l.ctl.WriteString("headers") if err != nil { - return nil, err + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err} } l.data, err = os.OpenFile(l.dir+"/data", os.O_RDWR, 0) if err != nil { - return nil, err + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err} } fd, err := l.netFD() return newUDPConn(fd), err } // ListenMulticastUDP listens for incoming multicast UDP packets -// addressed to the group address gaddr on ifi, which specifies the -// interface to join. ListenMulticastUDP uses default multicast -// interface if ifi is nil. -func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error) { - return nil, syscall.EPLAN9 +// addressed to the group address gaddr on the interface ifi. +// Network must be "udp", "udp4" or "udp6". +// ListenMulticastUDP uses the system-assigned multicast interface +// when ifi is nil, although this is not recommended because the +// assignment depends on platforms and sometimes it might require +// routing configuration. +// +// ListenMulticastUDP is just for convenience of simple, small +// applications. There are golang.org/x/net/ipv4 and +// golang.org/x/net/ipv6 packages for general purpose uses. +func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error) { + return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: syscall.EPLAN9} } diff --git a/libgo/go/net/udpsock_posix.go b/libgo/go/net/udpsock_posix.go index a0533366a42..61868c4b0cf 100644 --- a/libgo/go/net/udpsock_posix.go +++ b/libgo/go/net/udpsock_posix.go @@ -31,13 +31,6 @@ func (a *UDPAddr) family() int { return syscall.AF_INET6 } -func (a *UDPAddr) isWildcard() bool { - if a == nil || a.IP == nil { - return true - } - return a.IP.IsUnspecified() -} - func (a *UDPAddr) sockaddr(family int) (syscall.Sockaddr, error) { if a == nil { return nil, nil @@ -60,10 +53,11 @@ func newUDPConn(fd *netFD) *UDPConn { return &UDPConn{conn{fd}} } // ReadFromUDP can be made to time out and return an error with // Timeout() == true after a fixed time limit; see SetDeadline and // SetReadDeadline. -func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { +func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error) { if !c.ok() { return 0, nil, syscall.EINVAL } + var addr *UDPAddr n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrInet4: @@ -71,7 +65,10 @@ func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { case *syscall.SockaddrInet6: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneToString(int(sa.ZoneId))} } - return + if err != nil { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, addr, err } // ReadFrom implements the PacketConn ReadFrom method. @@ -80,7 +77,10 @@ func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) { return 0, nil, syscall.EINVAL } n, addr, err := c.ReadFromUDP(b) - return n, addr.toAddr(), err + if addr == nil { + return n, nil, err + } + return n, addr, err } // ReadMsgUDP reads a packet from c, copying the payload into b and @@ -100,6 +100,9 @@ func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, case *syscall.SockaddrInet6: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneToString(int(sa.ZoneId))} } + if err != nil { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } return } @@ -115,16 +118,20 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) { return 0, syscall.EINVAL } if c.fd.isConnected { - return 0, &OpError{"write", c.fd.net, addr, ErrWriteToConnected} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: ErrWriteToConnected} } if addr == nil { - return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: nil, Err: errMissingAddress} } sa, err := addr.sockaddr(c.fd.family) if err != nil { - return 0, &OpError{"write", c.fd.net, addr, err} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} } - return c.fd.writeTo(b, sa) + n, err := c.fd.writeTo(b, sa) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + return n, err } // WriteTo implements the PacketConn WriteTo method. @@ -134,29 +141,36 @@ func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) { } a, ok := addr.(*UDPAddr) if !ok { - return 0, &OpError{"write", c.fd.net, addr, syscall.EINVAL} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL} } return c.WriteToUDP(b, a) } -// WriteMsgUDP writes a packet to addr via c, copying the payload from -// b and the associated out-of-band data from oob. It returns the -// number of payload and out-of-band bytes written. +// WriteMsgUDP writes a packet to addr via c if c isn't connected, or +// to c's remote destination address if c is connected (in which case +// addr must be nil). The payload is copied from b and the associated +// out-of-band data is copied from oob. It returns the number of +// payload and out-of-band bytes written. func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) { if !c.ok() { return 0, 0, syscall.EINVAL } - if c.fd.isConnected { - return 0, 0, &OpError{"write", c.fd.net, addr, ErrWriteToConnected} + if c.fd.isConnected && addr != nil { + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: ErrWriteToConnected} } - if addr == nil { - return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} + if !c.fd.isConnected && addr == nil { + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: errMissingAddress} } - sa, err := addr.sockaddr(c.fd.family) + var sa syscall.Sockaddr + sa, err = addr.sockaddr(c.fd.family) if err != nil { - return 0, 0, &OpError{"write", c.fd.net, addr, err} + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} } - return c.fd.writeMsg(b, oob, sa) + n, oobn, err = c.fd.writeMsg(b, oob, sa) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + return } // DialUDP connects to the remote address raddr on the network net, @@ -166,10 +180,10 @@ func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) { switch net { case "udp", "udp4", "udp6": default: - return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(net)} } if raddr == nil { - return nil, &OpError{Op: "dial", Net: net, Addr: nil, Err: errMissingAddress} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress} } return dialUDP(net, laddr, raddr, noDeadline) } @@ -177,7 +191,7 @@ func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) { func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) { fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial") if err != nil { - return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: err} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err} } return newUDPConn(fd), nil } @@ -193,45 +207,52 @@ func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) { switch net { case "udp", "udp4", "udp6": default: - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(net)} } if laddr == nil { laddr = &UDPAddr{} } fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen") if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err} } return newUDPConn(fd), nil } // ListenMulticastUDP listens for incoming multicast UDP packets -// addressed to the group address gaddr on ifi, which specifies the -// interface to join. ListenMulticastUDP uses default multicast -// interface if ifi is nil. -func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error) { - switch net { +// addressed to the group address gaddr on the interface ifi. +// Network must be "udp", "udp4" or "udp6". +// ListenMulticastUDP uses the system-assigned multicast interface +// when ifi is nil, although this is not recommended because the +// assignment depends on platforms and sometimes it might require +// routing configuration. +// +// ListenMulticastUDP is just for convenience of simple, small +// applications. There are golang.org/x/net/ipv4 and +// golang.org/x/net/ipv6 packages for general purpose uses. +func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error) { + switch network { case "udp", "udp4", "udp6": default: - return nil, &OpError{Op: "listen", Net: net, Addr: gaddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: UnknownNetworkError(network)} } if gaddr == nil || gaddr.IP == nil { - return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: errMissingAddress} + return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: errMissingAddress} } - fd, err := internetSocket(net, gaddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen") + fd, err := internetSocket(network, gaddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen") if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: gaddr, Err: err} + return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr, Err: err} } c := newUDPConn(fd) if ip4 := gaddr.IP.To4(); ip4 != nil { if err := listenIPv4MulticastUDP(c, ifi, ip4); err != nil { c.Close() - return nil, &OpError{Op: "listen", Net: net, Addr: &IPAddr{IP: ip4}, Err: err} + return nil, &OpError{Op: "listen", Net: network, Source: c.fd.laddr, Addr: &IPAddr{IP: ip4}, Err: err} } } else { if err := listenIPv6MulticastUDP(c, ifi, gaddr.IP); err != nil { c.Close() - return nil, &OpError{Op: "listen", Net: net, Addr: &IPAddr{IP: gaddr.IP}, Err: err} + return nil, &OpError{Op: "listen", Net: network, Source: c.fd.laddr, Addr: &IPAddr{IP: gaddr.IP}, Err: err} } } return c, nil diff --git a/libgo/go/net/unicast_posix_test.go b/libgo/go/net/unicast_posix_test.go deleted file mode 100644 index ab7ef40a758..00000000000 --- a/libgo/go/net/unicast_posix_test.go +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright 2011 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 !plan9 - -package net - -import ( - "runtime" - "syscall" - "testing" -) - -var listenerTests = []struct { - net string - laddr string - ipv6 bool // test with underlying AF_INET6 socket - wildcard bool // test with wildcard address -}{ - {net: "tcp", laddr: "", wildcard: true}, - {net: "tcp", laddr: "0.0.0.0", wildcard: true}, - {net: "tcp", laddr: "[::ffff:0.0.0.0]", wildcard: true}, - {net: "tcp", laddr: "[::]", ipv6: true, wildcard: true}, - - {net: "tcp", laddr: "127.0.0.1"}, - {net: "tcp", laddr: "[::ffff:127.0.0.1]"}, - {net: "tcp", laddr: "[::1]", ipv6: true}, - - {net: "tcp4", laddr: "", wildcard: true}, - {net: "tcp4", laddr: "0.0.0.0", wildcard: true}, - {net: "tcp4", laddr: "[::ffff:0.0.0.0]", wildcard: true}, - - {net: "tcp4", laddr: "127.0.0.1"}, - {net: "tcp4", laddr: "[::ffff:127.0.0.1]"}, - - {net: "tcp6", laddr: "", ipv6: true, wildcard: true}, - {net: "tcp6", laddr: "[::]", ipv6: true, wildcard: true}, - - {net: "tcp6", laddr: "[::1]", ipv6: true}, -} - -// TestTCPListener tests both single and double listen to a test -// listener with same address family, same listening address and -// same port. -func TestTCPListener(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - for _, tt := range listenerTests { - if tt.wildcard && (testing.Short() || !*testExternal) { - continue - } - if tt.ipv6 && !supportsIPv6 { - continue - } - l1, port := usableListenPort(t, tt.net, tt.laddr) - checkFirstListener(t, tt.net, tt.laddr+":"+port, l1) - l2, err := Listen(tt.net, tt.laddr+":"+port) - checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2) - l1.Close() - } -} - -// TestUDPListener tests both single and double listen to a test -// listener with same address family, same listening address and -// same port. -func TestUDPListener(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - toudpnet := func(net string) string { - switch net { - case "tcp": - return "udp" - case "tcp4": - return "udp4" - case "tcp6": - return "udp6" - } - return "<nil>" - } - - for _, tt := range listenerTests { - if tt.wildcard && (testing.Short() || !*testExternal) { - continue - } - if tt.ipv6 && !supportsIPv6 { - continue - } - tt.net = toudpnet(tt.net) - l1, port := usableListenPacketPort(t, tt.net, tt.laddr) - checkFirstListener(t, tt.net, tt.laddr+":"+port, l1) - l2, err := ListenPacket(tt.net, tt.laddr+":"+port) - checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2) - l1.Close() - } -} - -var dualStackListenerTests = []struct { - net1 string // first listener - laddr1 string - net2 string // second listener - laddr2 string - wildcard bool // test with wildcard address - xerr error // expected error value, nil or other -}{ - // 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" "[::]" - - - ok - // "tcp" "[::]" "tcp" "" - - - ok - // "tcp" "0.0.0.0" "tcp" "[::]" - - - ok - // "tcp" "[::]" "tcp" "0.0.0.0" - - - ok - // "tcp" "[::ffff:0.0.0.0]" "tcp" "[::]" - - - ok - // "tcp" "[::]" "tcp" "[::ffff:0.0.0.0]" - - - ok - // ------------------------------------------------------------------------------------ - // "tcp4" "" "tcp6" "" ok ok ok ok - // "tcp6" "" "tcp4" "" ok ok ok ok - // "tcp4" "0.0.0.0" "tcp6" "[::]" ok ok ok ok - // "tcp6" "[::]" "tcp4" "0.0.0.0" ok ok ok ok - // ------------------------------------------------------------------------------------ - // "tcp" "127.0.0.1" "tcp" "[::1]" ok ok ok ok - // "tcp" "[::1]" "tcp" "127.0.0.1" ok ok ok ok - // "tcp4" "127.0.0.1" "tcp6" "[::1]" ok ok ok ok - // "tcp6" "[::1]" "tcp4" "127.0.0.1" ok ok ok ok - // - // Platform default configurations: - // darwin, kernel version 11.3.0 - // net.inet6.ip6.v6only=0 (overridable by sysctl or IPV6_V6ONLY option) - // freebsd, kernel version 8.2 - // net.inet6.ip6.v6only=1 (overridable by sysctl or IPV6_V6ONLY option) - // linux, kernel version 3.0.0 - // net.ipv6.bindv6only=0 (overridable by sysctl or IPV6_V6ONLY option) - // openbsd, kernel version 5.0 - // net.inet6.ip6.v6only=1 (overriding is prohibited) - - {net1: "tcp", laddr1: "", net2: "tcp", laddr2: "", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "", net2: "tcp", laddr2: "0.0.0.0", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "0.0.0.0", net2: "tcp", laddr2: "", wildcard: true, xerr: syscall.EADDRINUSE}, - - {net1: "tcp", laddr1: "", net2: "tcp", laddr2: "[::]", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "[::]", net2: "tcp", laddr2: "", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "0.0.0.0", net2: "tcp", laddr2: "[::]", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "[::]", net2: "tcp", laddr2: "0.0.0.0", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "[::ffff:0.0.0.0]", net2: "tcp", laddr2: "[::]", wildcard: true, xerr: syscall.EADDRINUSE}, - {net1: "tcp", laddr1: "[::]", net2: "tcp", laddr2: "[::ffff:0.0.0.0]", wildcard: true, xerr: syscall.EADDRINUSE}, - - {net1: "tcp4", laddr1: "", net2: "tcp6", laddr2: "", wildcard: true}, - {net1: "tcp6", laddr1: "", net2: "tcp4", laddr2: "", wildcard: true}, - {net1: "tcp4", laddr1: "0.0.0.0", net2: "tcp6", laddr2: "[::]", wildcard: true}, - {net1: "tcp6", laddr1: "[::]", net2: "tcp4", laddr2: "0.0.0.0", wildcard: true}, - - {net1: "tcp", laddr1: "127.0.0.1", net2: "tcp", laddr2: "[::1]"}, - {net1: "tcp", laddr1: "[::1]", net2: "tcp", laddr2: "127.0.0.1"}, - {net1: "tcp4", laddr1: "127.0.0.1", net2: "tcp6", laddr2: "[::1]"}, - {net1: "tcp6", laddr1: "[::1]", net2: "tcp4", laddr2: "127.0.0.1"}, -} - -// TestDualStackTCPListener tests both single and double listen -// to a test listener with various address families, different -// listening address and same port. -func TestDualStackTCPListener(t *testing.T) { - if testing.Short() { - t.Skip("skipping in -short mode, see issue 5001") - } - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - if !supportsIPv6 { - t.Skip("ipv6 is not supported") - } - - for _, tt := range dualStackListenerTests { - if tt.wildcard && !*testExternal { - continue - } - switch runtime.GOOS { - case "openbsd": - if tt.wildcard && differentWildcardAddr(tt.laddr1, tt.laddr2) { - tt.xerr = nil - } - } - l1, port := usableListenPort(t, tt.net1, tt.laddr1) - laddr := tt.laddr1 + ":" + port - checkFirstListener(t, tt.net1, laddr, l1) - laddr = tt.laddr2 + ":" + port - l2, err := Listen(tt.net2, laddr) - checkDualStackSecondListener(t, tt.net2, laddr, tt.xerr, err, l2) - l1.Close() - } -} - -// TestDualStackUDPListener tests both single and double listen -// to a test listener with various address families, differnet -// listening address and same port. -func TestDualStackUDPListener(t *testing.T) { - if testing.Short() { - t.Skip("skipping in -short mode, see issue 5001") - } - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - if !supportsIPv6 { - t.Skip("ipv6 is not supported") - } - - toudpnet := func(net string) string { - switch net { - case "tcp": - return "udp" - case "tcp4": - return "udp4" - case "tcp6": - return "udp6" - } - return "<nil>" - } - - for _, tt := range dualStackListenerTests { - if tt.wildcard && (testing.Short() || !*testExternal) { - continue - } - tt.net1 = toudpnet(tt.net1) - tt.net2 = toudpnet(tt.net2) - switch runtime.GOOS { - case "openbsd": - if tt.wildcard && differentWildcardAddr(tt.laddr1, tt.laddr2) { - tt.xerr = nil - } - } - l1, port := usableListenPacketPort(t, tt.net1, tt.laddr1) - laddr := tt.laddr1 + ":" + port - checkFirstListener(t, tt.net1, laddr, l1) - laddr = tt.laddr2 + ":" + port - l2, err := ListenPacket(tt.net2, laddr) - checkDualStackSecondListener(t, tt.net2, laddr, tt.xerr, err, l2) - l1.Close() - } -} - -func usableListenPort(t *testing.T, net, laddr string) (l Listener, port string) { - var nladdr string - var err error - switch net { - default: - panic("usableListenPort net=" + net) - case "tcp", "tcp4", "tcp6": - l, err = Listen(net, laddr+":0") - if err != nil { - t.Fatalf("Probe Listen(%q, %q) failed: %v", net, laddr, err) - } - nladdr = l.(*TCPListener).Addr().String() - } - _, port, err = SplitHostPort(nladdr) - if err != nil { - t.Fatalf("SplitHostPort failed: %v", err) - } - return l, port -} - -func usableListenPacketPort(t *testing.T, net, laddr string) (l PacketConn, port string) { - var nladdr string - var err error - switch net { - default: - panic("usableListenPacketPort net=" + net) - case "udp", "udp4", "udp6": - l, err = ListenPacket(net, laddr+":0") - if err != nil { - t.Fatalf("Probe ListenPacket(%q, %q) failed: %v", net, laddr, err) - } - nladdr = l.(*UDPConn).LocalAddr().String() - } - _, port, err = SplitHostPort(nladdr) - if err != nil { - t.Fatalf("SplitHostPort failed: %v", err) - } - return l, port -} - -func differentWildcardAddr(i, j string) bool { - if (i == "" || i == "0.0.0.0" || i == "::ffff:0.0.0.0") && (j == "" || j == "0.0.0.0" || j == "::ffff:0.0.0.0") { - return false - } - if i == "[::]" && j == "[::]" { - return false - } - return true -} - -func checkFirstListener(t *testing.T, net, laddr string, l interface{}) { - switch net { - case "tcp": - fd := l.(*TCPListener).fd - checkDualStackAddrFamily(t, net, laddr, fd) - case "tcp4": - fd := l.(*TCPListener).fd - if fd.family != syscall.AF_INET { - t.Fatalf("First Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET) - } - case "tcp6": - fd := l.(*TCPListener).fd - if fd.family != syscall.AF_INET6 { - t.Fatalf("First Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6) - } - case "udp": - fd := l.(*UDPConn).fd - checkDualStackAddrFamily(t, net, laddr, fd) - case "udp4": - fd := l.(*UDPConn).fd - if fd.family != syscall.AF_INET { - t.Fatalf("First ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET) - } - case "udp6": - fd := l.(*UDPConn).fd - if fd.family != syscall.AF_INET6 { - t.Fatalf("First ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6) - } - default: - t.Fatalf("Unexpected network: %q", net) - } -} - -func checkSecondListener(t *testing.T, net, laddr string, err error, l interface{}) { - switch net { - case "tcp", "tcp4", "tcp6": - if err == nil { - l.(*TCPListener).Close() - t.Fatalf("Second Listen(%q, %q) should fail", net, laddr) - } - case "udp", "udp4", "udp6": - if err == nil { - l.(*UDPConn).Close() - t.Fatalf("Second ListenPacket(%q, %q) should fail", net, laddr) - } - default: - t.Fatalf("Unexpected network: %q", net) - } -} - -func checkDualStackSecondListener(t *testing.T, net, laddr string, xerr, err error, l interface{}) { - switch net { - case "tcp", "tcp4", "tcp6": - if xerr == nil && err != nil || xerr != nil && err == nil { - t.Fatalf("Second Listen(%q, %q) returns %v, expected %v", net, laddr, err, xerr) - } - if err == nil { - l.(*TCPListener).Close() - } - case "udp", "udp4", "udp6": - if xerr == nil && err != nil || xerr != nil && err == nil { - t.Fatalf("Second ListenPacket(%q, %q) returns %v, expected %v", net, laddr, err, xerr) - } - if err == nil { - l.(*UDPConn).Close() - } - default: - t.Fatalf("Unexpected network: %q", net) - } -} - -func checkDualStackAddrFamily(t *testing.T, net, laddr string, fd *netFD) { - switch a := fd.laddr.(type) { - case *TCPAddr: - // If a node under test supports both IPv6 capability - // and IPv6 IPv4-mapping capability, we can assume - // that the node listens on a wildcard address with an - // AF_INET6 socket. - if supportsIPv4map && fd.laddr.(*TCPAddr).isWildcard() { - if fd.family != syscall.AF_INET6 { - t.Fatalf("Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6) - } - } else { - if fd.family != a.family() { - t.Fatalf("Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, a.family()) - } - } - case *UDPAddr: - // If a node under test supports both IPv6 capability - // and IPv6 IPv4-mapping capability, we can assume - // that the node listens on a wildcard address with an - // AF_INET6 socket. - if supportsIPv4map && fd.laddr.(*UDPAddr).isWildcard() { - if fd.family != syscall.AF_INET6 { - t.Fatalf("ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6) - } - } else { - if fd.family != a.family() { - t.Fatalf("ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, a.family()) - } - } - default: - t.Fatalf("Unexpected protocol address type: %T", a) - } -} - -var prohibitionaryDialArgTests = []struct { - net string - addr string -}{ - {"tcp6", "127.0.0.1"}, - {"tcp6", "[::ffff:127.0.0.1]"}, -} - -func TestProhibitionaryDialArgs(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - // This test requires both IPv6 and IPv6 IPv4-mapping functionality. - if !supportsIPv4map || testing.Short() || !*testExternal { - return - } - - l, port := usableListenPort(t, "tcp", "[::]") - defer l.Close() - - for _, tt := range prohibitionaryDialArgTests { - c, err := Dial(tt.net, tt.addr+":"+port) - if err == nil { - c.Close() - t.Fatalf("Dial(%q, %q) should fail", tt.net, tt.addr) - } - } -} - -func TestWildWildcardListener(t *testing.T) { - switch runtime.GOOS { - case "plan9": - t.Skipf("skipping test on %q", runtime.GOOS) - } - - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } - - defer func() { - if p := recover(); p != nil { - t.Fatalf("Listen, ListenPacket or protocol-specific Listen panicked: %v", p) - } - }() - - if ln, err := Listen("tcp", ""); err == nil { - ln.Close() - } - if ln, err := ListenPacket("udp", ""); err == nil { - ln.Close() - } - if ln, err := ListenTCP("tcp", nil); err == nil { - ln.Close() - } - if ln, err := ListenUDP("udp", nil); err == nil { - ln.Close() - } - if ln, err := ListenIP("ip:icmp", nil); err == nil { - ln.Close() - } -} diff --git a/libgo/go/net/unix_test.go b/libgo/go/net/unix_test.go index 1cdff3908c1..358ff310725 100644 --- a/libgo/go/net/unix_test.go +++ b/libgo/go/net/unix_test.go @@ -17,14 +17,18 @@ import ( ) func TestReadUnixgramWithUnnamedSocket(t *testing.T) { + if !testableNetwork("unixgram") { + t.Skip("unixgram test") + } + addr := testUnixAddr() la, err := ResolveUnixAddr("unixgram", addr) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c, err := ListenUnixgram("unixgram", la) if err != nil { - t.Fatalf("ListenUnixgram failed: %v", err) + t.Fatal(err) } defer func() { c.Close() @@ -37,13 +41,13 @@ func TestReadUnixgramWithUnnamedSocket(t *testing.T) { defer func() { off <- true }() s, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) if err != nil { - t.Errorf("syscall.Socket failed: %v", err) + t.Error(err) return } defer syscall.Close(s) rsa := &syscall.SockaddrUnix{Name: addr} if err := syscall.Sendto(s, data[:], 0, rsa); err != nil { - t.Errorf("syscall.Sendto failed: %v", err) + t.Error(err) return } }() @@ -53,69 +57,123 @@ func TestReadUnixgramWithUnnamedSocket(t *testing.T) { c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) n, from, err := c.ReadFrom(b) if err != nil { - t.Fatalf("UnixConn.ReadFrom failed: %v", err) + t.Fatal(err) } if from != nil { - t.Fatalf("neighbor address is %v", from) + t.Fatalf("unexpected peer address: %v", from) } if !bytes.Equal(b[:n], data[:]) { - t.Fatalf("got %v, want %v", b[:n], data[:]) + t.Fatalf("got %v; want %v", b[:n], data[:]) } } -func TestReadUnixgramWithZeroBytesBuffer(t *testing.T) { - // issue 4352: Recvfrom failed with "address family not - // supported by protocol family" if zero-length buffer provided +func TestUnixgramZeroBytePayload(t *testing.T) { + if !testableNetwork("unixgram") { + t.Skip("unixgram test") + } - addr := testUnixAddr() - la, err := ResolveUnixAddr("unixgram", addr) + c1, err := newLocalPacketListener("unixgram") if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } - c, err := ListenUnixgram("unixgram", la) + defer os.Remove(c1.LocalAddr().String()) + defer c1.Close() + + c2, err := Dial("unixgram", c1.LocalAddr().String()) if err != nil { - t.Fatalf("ListenUnixgram failed: %v", err) + t.Fatal(err) } - defer func() { - c.Close() - os.Remove(addr) - }() + defer os.Remove(c2.LocalAddr().String()) + defer c2.Close() - off := make(chan bool) - go func() { - defer func() { off <- true }() - c, err := DialUnix("unixgram", nil, la) + for _, genericRead := range []bool{false, true} { + n, err := c2.Write(nil) if err != nil { - t.Errorf("DialUnix failed: %v", err) - return + t.Fatal(err) } - defer c.Close() - if _, err := c.Write([]byte{1, 2, 3, 4, 5}); err != nil { - t.Errorf("UnixConn.Write failed: %v", err) - return + if n != 0 { + t.Errorf("got %d; want 0", n) } - }() + c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + var b [1]byte + var peer Addr + if genericRead { + _, err = c1.(Conn).Read(b[:]) + } else { + _, peer, err = c1.ReadFrom(b[:]) + } + switch err { + case nil: // ReadFrom succeeds + if peer != nil { // peer is connected-mode + t.Fatalf("unexpected peer address: %v", peer) + } + default: // Read may timeout, it depends on the platform + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) + } + } + } +} - <-off - c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - _, from, err := c.ReadFrom(nil) +func TestUnixgramZeroByteBuffer(t *testing.T) { + if !testableNetwork("unixgram") { + t.Skip("unixgram test") + } + // issue 4352: Recvfrom failed with "address family not + // supported by protocol family" if zero-length buffer provided + + c1, err := newLocalPacketListener("unixgram") if err != nil { - t.Fatalf("UnixConn.ReadFrom failed: %v", err) + t.Fatal(err) } - if from != nil { - t.Fatalf("neighbor address is %v", from) + defer os.Remove(c1.LocalAddr().String()) + defer c1.Close() + + c2, err := Dial("unixgram", c1.LocalAddr().String()) + if err != nil { + t.Fatal(err) + } + defer os.Remove(c2.LocalAddr().String()) + defer c2.Close() + + b := []byte("UNIXGRAM ZERO BYTE BUFFER TEST") + for _, genericRead := range []bool{false, true} { + n, err := c2.Write(b) + if err != nil { + t.Fatal(err) + } + if n != len(b) { + t.Errorf("got %d; want %d", n, len(b)) + } + c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + var peer Addr + if genericRead { + _, err = c1.(Conn).Read(nil) + } else { + _, peer, err = c1.ReadFrom(nil) + } + switch err { + case nil: // ReadFrom succeeds + if peer != nil { // peer is connected-mode + t.Fatalf("unexpected peer address: %v", peer) + } + default: // Read may timeout, it depends on the platform + if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + t.Fatal(err) + } + } } } func TestUnixgramAutobind(t *testing.T) { if runtime.GOOS != "linux" { - t.Skip("skipping: autobind is linux only") + t.Skip("autobind is linux only") } laddr := &UnixAddr{Name: "", Net: "unixgram"} c1, err := ListenUnixgram("unixgram", laddr) if err != nil { - t.Fatalf("ListenUnixgram failed: %v", err) + t.Fatal(err) } defer c1.Close() @@ -130,7 +188,7 @@ func TestUnixgramAutobind(t *testing.T) { c2, err := DialUnix("unixgram", nil, autoAddr) if err != nil { - t.Fatalf("DialUnix failed: %v", err) + t.Fatal(err) } defer c2.Close() @@ -141,25 +199,30 @@ func TestUnixgramAutobind(t *testing.T) { func TestUnixAutobindClose(t *testing.T) { if runtime.GOOS != "linux" { - t.Skip("skipping: autobind is linux only") + t.Skip("autobind is linux only") } + laddr := &UnixAddr{Name: "", Net: "unix"} ln, err := ListenUnix("unix", laddr) if err != nil { - t.Fatalf("ListenUnix failed: %v", err) + t.Fatal(err) } ln.Close() } func TestUnixgramWrite(t *testing.T) { + if !testableNetwork("unixgram") { + t.Skip("unixgram test") + } + addr := testUnixAddr() laddr, err := ResolveUnixAddr("unixgram", addr) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c, err := ListenPacket("unixgram", addr) if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } defer os.Remove(addr) defer c.Close() @@ -171,27 +234,28 @@ func TestUnixgramWrite(t *testing.T) { func testUnixgramWriteConn(t *testing.T, raddr *UnixAddr) { c, err := Dial("unixgram", raddr.String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + t.Fatal(err) } defer c.Close() - if _, err := c.(*UnixConn).WriteToUnix([]byte("Connection-oriented mode socket"), raddr); err == nil { - t.Fatal("WriteToUnix should fail") + b := []byte("CONNECTED-MODE SOCKET") + if _, err := c.(*UnixConn).WriteToUnix(b, raddr); err == nil { + t.Fatal("should fail") } else if err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteToUnix should fail as ErrWriteToConnected: %v", err) + t.Fatalf("should fail as ErrWriteToConnected: %v", err) } - if _, err = c.(*UnixConn).WriteTo([]byte("Connection-oriented mode socket"), raddr); err == nil { - t.Fatal("WriteTo should fail") + if _, err = c.(*UnixConn).WriteTo(b, raddr); err == nil { + t.Fatal("should fail") } else if err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteTo should fail as ErrWriteToConnected: %v", err) + t.Fatalf("should fail as ErrWriteToConnected: %v", err) } - if _, _, err = c.(*UnixConn).WriteMsgUnix([]byte("Connection-oriented mode socket"), nil, raddr); err == nil { - t.Fatal("WriteTo should fail") + if _, _, err = c.(*UnixConn).WriteMsgUnix(b, nil, raddr); err == nil { + t.Fatal("should fail") } else if err.(*OpError).Err != ErrWriteToConnected { - t.Fatalf("WriteMsgUnix should fail as ErrWriteToConnected: %v", err) + t.Fatalf("should fail as ErrWriteToConnected: %v", err) } - if _, err := c.Write([]byte("Connection-oriented mode socket")); err != nil { - t.Fatalf("Write failed: %v", err) + if _, err := c.Write(b); err != nil { + t.Fatal(err) } } @@ -199,52 +263,59 @@ func testUnixgramWritePacketConn(t *testing.T, raddr *UnixAddr) { addr := testUnixAddr() c, err := ListenPacket("unixgram", addr) if err != nil { - t.Fatalf("ListenPacket failed: %v", err) + t.Fatal(err) } defer os.Remove(addr) defer c.Close() - if _, err := c.(*UnixConn).WriteToUnix([]byte("Connectionless mode socket"), raddr); err != nil { - t.Fatalf("WriteToUnix failed: %v", err) + b := []byte("UNCONNECTED-MODE SOCKET") + if _, err := c.(*UnixConn).WriteToUnix(b, raddr); err != nil { + t.Fatal(err) } - if _, err := c.WriteTo([]byte("Connectionless mode socket"), raddr); err != nil { - t.Fatalf("WriteTo failed: %v", err) + if _, err := c.WriteTo(b, raddr); err != nil { + t.Fatal(err) } - if _, _, err := c.(*UnixConn).WriteMsgUnix([]byte("Connectionless mode socket"), nil, raddr); err != nil { - t.Fatalf("WriteMsgUnix failed: %v", err) + if _, _, err := c.(*UnixConn).WriteMsgUnix(b, nil, raddr); err != nil { + t.Fatal(err) } - if _, err := c.(*UnixConn).Write([]byte("Connectionless mode socket")); err == nil { - t.Fatal("Write should fail") + if _, err := c.(*UnixConn).Write(b); err == nil { + t.Fatal("should fail") } } func TestUnixConnLocalAndRemoteNames(t *testing.T) { + if !testableNetwork("unix") { + t.Skip("unix test") + } + + handler := func(ls *localServer, ln Listener) {} for _, laddr := range []string{"", testUnixAddr()} { laddr := laddr taddr := testUnixAddr() ta, err := ResolveUnixAddr("unix", taddr) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } ln, err := ListenUnix("unix", ta) if err != nil { - t.Fatalf("ListenUnix failed: %v", err) + t.Fatal(err) + } + ls, err := (&streamListener{Listener: ln}).newLocalServer() + if err != nil { + t.Fatal(err) + } + defer ls.teardown() + if err := ls.buildup(handler); err != nil { + t.Fatal(err) } - defer func() { - ln.Close() - os.Remove(taddr) - }() - - done := make(chan int) - go transponder(t, ln, done) la, err := ResolveUnixAddr("unix", laddr) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c, err := DialUnix("unix", la, ta) if err != nil { - t.Fatalf("DialUnix failed: %v", err) + t.Fatal(err) } defer func() { c.Close() @@ -253,7 +324,7 @@ func TestUnixConnLocalAndRemoteNames(t *testing.T) { } }() if _, err := c.Write([]byte("UNIXCONN LOCAL AND REMOTE NAME TEST")); err != nil { - t.Fatalf("UnixConn.Write failed: %v", err) + t.Fatal(err) } switch runtime.GOOS { @@ -272,22 +343,24 @@ func TestUnixConnLocalAndRemoteNames(t *testing.T) { t.Fatalf("got %#v, expected %#v", ca.got, ca.want) } } - - <-done } } func TestUnixgramConnLocalAndRemoteNames(t *testing.T) { + if !testableNetwork("unixgram") { + t.Skip("unixgram test") + } + for _, laddr := range []string{"", testUnixAddr()} { laddr := laddr taddr := testUnixAddr() ta, err := ResolveUnixAddr("unixgram", taddr) if err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } c1, err := ListenUnixgram("unixgram", ta) if err != nil { - t.Fatalf("ListenUnixgram failed: %v", err) + t.Fatal(err) } defer func() { c1.Close() @@ -297,12 +370,12 @@ func TestUnixgramConnLocalAndRemoteNames(t *testing.T) { var la *UnixAddr if laddr != "" { if la, err = ResolveUnixAddr("unixgram", laddr); err != nil { - t.Fatalf("ResolveUnixAddr failed: %v", err) + t.Fatal(err) } } c2, err := DialUnix("unixgram", la, ta) if err != nil { - t.Fatalf("DialUnix failed: %v", err) + t.Fatal(err) } defer func() { c2.Close() @@ -326,8 +399,33 @@ func TestUnixgramConnLocalAndRemoteNames(t *testing.T) { } for _, ca := range connAddrs { if !reflect.DeepEqual(ca.got, ca.want) { - t.Fatalf("got %#v, expected %#v", ca.got, ca.want) + t.Fatalf("got %#v; want %#v", ca.got, ca.want) } } } } + +// forceGoDNS forces the resolver configuration to use the pure Go resolver +// and returns a fixup function to restore the old settings. +func forceGoDNS() func() { + c := systemConf() + oldGo := c.netGo + oldCgo := c.netCgo + fixup := func() { + c.netGo = oldGo + c.netCgo = oldCgo + } + c.netGo = true + c.netCgo = false + return fixup +} + +// forceCgoDNS forces the resolver configuration to use the cgo resolver +// and returns true to indicate that it did so. +// (On non-Unix systems forceCgoDNS returns false.) +func forceCgoDNS() bool { + c := systemConf() + c.netGo = false + c.netCgo = true + return true +} diff --git a/libgo/go/net/unixsock.go b/libgo/go/net/unixsock.go index 85955845b80..eb91d0d6309 100644 --- a/libgo/go/net/unixsock.go +++ b/libgo/go/net/unixsock.go @@ -23,7 +23,11 @@ func (a *UnixAddr) String() string { return a.Name } -func (a *UnixAddr) toAddr() Addr { +func (a *UnixAddr) isWildcard() bool { + return a == nil || a.Name == "" +} + +func (a *UnixAddr) opAddr() Addr { if a == nil { return nil } diff --git a/libgo/go/net/unixsock_plan9.go b/libgo/go/net/unixsock_plan9.go index c60c1d83bb3..84b6b600f66 100644 --- a/libgo/go/net/unixsock_plan9.go +++ b/libgo/go/net/unixsock_plan9.go @@ -24,12 +24,12 @@ type UnixConn struct { // Timeout() == true after a fixed time limit; see SetDeadline and // SetReadDeadline. func (c *UnixConn) ReadFromUnix(b []byte) (int, *UnixAddr, error) { - return 0, nil, syscall.EPLAN9 + return 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // ReadFrom implements the PacketConn ReadFrom method. func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error) { - return 0, nil, syscall.EPLAN9 + return 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // ReadMsgUnix reads a packet from c, copying the payload into b and @@ -37,7 +37,7 @@ func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error) { // bytes copied into b, the number of bytes copied into oob, the flags // that were set on the packet, and the source address of the packet. func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error) { - return 0, 0, 0, nil, syscall.EPLAN9 + return 0, 0, 0, nil, &OpError{Op: "read", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // WriteToUnix writes a packet to addr via c, copying the payload from b. @@ -47,31 +47,31 @@ func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAdd // SetWriteDeadline. On packet-oriented connections, write timeouts // are rare. func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (int, error) { - return 0, syscall.EPLAN9 + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EPLAN9} } // WriteTo implements the PacketConn WriteTo method. func (c *UnixConn) WriteTo(b []byte, addr Addr) (int, error) { - return 0, syscall.EPLAN9 + return 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr, Err: syscall.EPLAN9} } // WriteMsgUnix writes a packet to addr via c, copying the payload // from b and the associated out-of-band data from oob. It returns // the number of payload and out-of-band bytes written. func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error) { - return 0, 0, syscall.EPLAN9 + return 0, 0, &OpError{Op: "write", Net: c.fd.dir, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EPLAN9} } // CloseRead shuts down the reading side of the Unix domain connection. // Most callers should just use Close. func (c *UnixConn) CloseRead() error { - return syscall.EPLAN9 + return &OpError{Op: "close", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // CloseWrite shuts down the writing side of the Unix domain connection. // Most callers should just use Close. func (c *UnixConn) CloseWrite() error { - return syscall.EPLAN9 + return &OpError{Op: "close", Net: c.fd.dir, Source: c.fd.laddr, Addr: c.fd.raddr, Err: syscall.EPLAN9} } // DialUnix connects to the remote address raddr on the network net, @@ -82,45 +82,49 @@ func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { } func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: syscall.EPLAN9} } // UnixListener is a Unix domain socket listener. Clients should // typically use variables of type Listener instead of assuming Unix // domain sockets. -type UnixListener struct{} +type UnixListener struct { + fd *netFD +} // ListenUnix announces on the Unix domain socket laddr and returns a // Unix listener. The network net must be "unix" or "unixpacket". func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: syscall.EPLAN9} } // AcceptUnix accepts the next incoming call and returns the new // connection. func (l *UnixListener) AcceptUnix() (*UnixConn, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "accept", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: syscall.EPLAN9} } // Accept implements the Accept method in the Listener interface; it // waits for the next call and returns a generic Conn. func (l *UnixListener) Accept() (Conn, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "accept", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: syscall.EPLAN9} } // Close stops listening on the Unix address. Already accepted // connections are not closed. func (l *UnixListener) Close() error { - return syscall.EPLAN9 + return &OpError{Op: "close", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: syscall.EPLAN9} } // Addr returns the listener's network address. +// The Addr returned is shared by all invocations of Addr, so +// do not modify it. func (l *UnixListener) Addr() Addr { return nil } // SetDeadline sets the deadline associated with the listener. // A zero time value disables the deadline. func (l *UnixListener) SetDeadline(t time.Time) error { - return syscall.EPLAN9 + return &OpError{Op: "set", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: syscall.EPLAN9} } // File returns a copy of the underlying os.File, set to blocking @@ -131,7 +135,7 @@ func (l *UnixListener) SetDeadline(t time.Time) error { // connection's. Attempting to change properties of the original // using this duplicate may or may not have the desired effect. func (l *UnixListener) File() (*os.File, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "file", Net: l.fd.dir, Source: nil, Addr: l.fd.laddr, Err: syscall.EPLAN9} } // ListenUnixgram listens for incoming Unix datagram packets addressed @@ -139,5 +143,5 @@ func (l *UnixListener) File() (*os.File, error) { // The returned connection's ReadFrom and WriteTo methods can be used // to receive and send packets with per-packet addressing. func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error) { - return nil, syscall.EPLAN9 + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: syscall.EPLAN9} } diff --git a/libgo/go/net/unixsock_posix.go b/libgo/go/net/unixsock_posix.go index 3c2e78bdca3..351d9b3a39a 100644 --- a/libgo/go/net/unixsock_posix.go +++ b/libgo/go/net/unixsock_posix.go @@ -87,10 +87,6 @@ func (a *UnixAddr) family() int { return syscall.AF_UNIX } -func (a *UnixAddr) isWildcard() bool { - return a == nil || a.Name == "" -} - func (a *UnixAddr) sockaddr(family int) (syscall.Sockaddr, error) { if a == nil { return nil, nil @@ -113,10 +109,11 @@ func newUnixConn(fd *netFD) *UnixConn { return &UnixConn{conn{fd}} } // ReadFromUnix can be made to time out and return an error with // Timeout() == true after a fixed time limit; see SetDeadline and // SetReadDeadline. -func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) { +func (c *UnixConn) ReadFromUnix(b []byte) (int, *UnixAddr, error) { if !c.ok() { return 0, nil, syscall.EINVAL } + var addr *UnixAddr n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrUnix: @@ -124,7 +121,10 @@ func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) { addr = &UnixAddr{Name: sa.Name, Net: sotypeToNet(c.fd.sotype)} } } - return + if err != nil { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, addr, err } // ReadFrom implements the PacketConn ReadFrom method. @@ -133,7 +133,10 @@ func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error) { return 0, nil, syscall.EINVAL } n, addr, err := c.ReadFromUnix(b) - return n, addr.toAddr(), err + if addr == nil { + return n, nil, err + } + return n, addr, err } // ReadMsgUnix reads a packet from c, copying the payload into b and @@ -151,6 +154,9 @@ func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAdd addr = &UnixAddr{Name: sa.Name, Net: sotypeToNet(c.fd.sotype)} } } + if err != nil { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } return } @@ -160,21 +166,25 @@ func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAdd // Timeout() == true after a fixed time limit; see SetDeadline and // SetWriteDeadline. On packet-oriented connections, write timeouts // are rare. -func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err error) { +func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (int, error) { if !c.ok() { return 0, syscall.EINVAL } if c.fd.isConnected { - return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: ErrWriteToConnected} } if addr == nil { - return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: nil, Err: errMissingAddress} } if addr.Net != sotypeToNet(c.fd.sotype) { - return 0, syscall.EAFNOSUPPORT + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EAFNOSUPPORT} } sa := &syscall.SockaddrUnix{Name: addr.Name} - return c.fd.writeTo(b, sa) + n, err := c.fd.writeTo(b, sa) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + return n, err } // WriteTo implements the PacketConn WriteTo method. @@ -184,7 +194,7 @@ func (c *UnixConn) WriteTo(b []byte, addr Addr) (n int, err error) { } a, ok := addr.(*UnixAddr) if !ok { - return 0, &OpError{"write", c.fd.net, addr, syscall.EINVAL} + return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL} } return c.WriteToUnix(b, a) } @@ -197,16 +207,20 @@ func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err return 0, 0, syscall.EINVAL } if c.fd.sotype == syscall.SOCK_DGRAM && c.fd.isConnected { - return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: ErrWriteToConnected} } + var sa syscall.Sockaddr if addr != nil { if addr.Net != sotypeToNet(c.fd.sotype) { - return 0, 0, syscall.EAFNOSUPPORT + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: syscall.EAFNOSUPPORT} } - sa := &syscall.SockaddrUnix{Name: addr.Name} - return c.fd.writeMsg(b, oob, sa) + sa = &syscall.SockaddrUnix{Name: addr.Name} } - return c.fd.writeMsg(b, oob, nil) + n, oobn, err = c.fd.writeMsg(b, oob, sa) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err} + } + return } // CloseRead shuts down the reading side of the Unix domain connection. @@ -215,7 +229,11 @@ func (c *UnixConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return c.fd.closeRead() + err := c.fd.closeRead() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // CloseWrite shuts down the writing side of the Unix domain connection. @@ -224,7 +242,11 @@ func (c *UnixConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } - return c.fd.closeWrite() + err := c.fd.closeWrite() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err } // DialUnix connects to the remote address raddr on the network net, @@ -234,7 +256,7 @@ func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { switch net { case "unix", "unixgram", "unixpacket": default: - return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(net)} } return dialUnix(net, laddr, raddr, noDeadline) } @@ -242,7 +264,7 @@ func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { 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, &OpError{Op: "dial", Net: net, Addr: raddr, Err: err} + return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err} } return newUnixConn(fd), nil } @@ -261,16 +283,16 @@ func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) { switch net { case "unix", "unixpacket": default: - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(net)} } if laddr == nil { - return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: errMissingAddress} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: errMissingAddress} } fd, err := unixSocket(net, laddr, nil, "listen", noDeadline) if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: err} } - return &UnixListener{fd, fd.laddr.String()}, nil + return &UnixListener{fd: fd, path: fd.laddr.String()}, nil } // AcceptUnix accepts the next incoming call and returns the new @@ -281,10 +303,9 @@ func (l *UnixListener) AcceptUnix() (*UnixConn, error) { } fd, err := l.fd.accept() if err != nil { - return nil, err + return nil, &OpError{Op: "accept", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} } - c := newUnixConn(fd) - return c, nil + return newUnixConn(fd), nil } // Accept implements the Accept method in the Listener interface; it @@ -317,19 +338,28 @@ func (l *UnixListener) Close() error { if l.path[0] != '@' { syscall.Unlink(l.path) } - return l.fd.Close() + err := l.fd.Close() + if err != nil { + err = &OpError{Op: "close", Net: l.fd.net, Source: l.fd.laddr, Addr: l.fd.raddr, Err: err} + } + return err } // Addr returns the listener's network address. +// The Addr returned is shared by all invocations of Addr, so +// do not modify it. func (l *UnixListener) Addr() Addr { return l.fd.laddr } // SetDeadline sets the deadline associated with the listener. // A zero time value disables the deadline. -func (l *UnixListener) SetDeadline(t time.Time) (err error) { +func (l *UnixListener) SetDeadline(t time.Time) error { if l == nil || l.fd == nil { return syscall.EINVAL } - return l.fd.setDeadline(t) + if err := l.fd.setDeadline(t); err != nil { + return &OpError{Op: "set", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} + } + return nil } // File returns a copy of the underlying os.File, set to blocking @@ -339,7 +369,13 @@ func (l *UnixListener) SetDeadline(t time.Time) (err error) { // The returned os.File's file descriptor is different from the // connection's. Attempting to change properties of the original // using this duplicate may or may not have the desired effect. -func (l *UnixListener) File() (f *os.File, err error) { return l.fd.dup() } +func (l *UnixListener) File() (f *os.File, err error) { + f, err = l.fd.dup() + if err != nil { + err = &OpError{Op: "file", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err} + } + return +} // ListenUnixgram listens for incoming Unix datagram packets addressed // to the local address laddr. The network net must be "unixgram". @@ -349,14 +385,14 @@ func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error) { switch net { case "unixgram": default: - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: UnknownNetworkError(net)} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(net)} } if laddr == nil { - return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: errMissingAddress} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: errMissingAddress} } fd, err := unixSocket(net, laddr, nil, "listen", noDeadline) if err != nil { - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} + return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr.opAddr(), Err: err} } return newUnixConn(fd), nil } diff --git a/libgo/go/net/url/url.go b/libgo/go/net/url/url.go index f167408faba..8ffad663d5c 100644 --- a/libgo/go/net/url/url.go +++ b/libgo/go/net/url/url.go @@ -9,6 +9,7 @@ package url import ( "bytes" "errors" + "fmt" "sort" "strconv" "strings" @@ -51,6 +52,7 @@ type encoding int const ( encodePath encoding = 1 + iota + encodeHost encodeUserPassword encodeQueryComponent encodeFragment @@ -64,12 +66,27 @@ func (e EscapeError) Error() string { // Return true if the specified character should be escaped when // appearing in a URL string, according to RFC 3986. +// +// Please be informed that for now shouldEscape does not check all +// reserved characters correctly. See golang.org/issue/5684. func shouldEscape(c byte, mode encoding) bool { // §2.3 Unreserved characters (alphanum) if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { return false } + if mode == encodeHost { + // §3.2.2 Host allows + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + // as part of reg-name. + // We add : because we include :port as part of host. + // We add [ ] because we include [ipv6]:port as part of host + switch c { + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']': + return false + } + } + switch c { case '-', '_', '.', '~': // §2.3 Unreserved characters (mark) return false @@ -127,7 +144,7 @@ func unescape(s string, mode encoding) (string, error) { if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { s = s[i:] if len(s) > 3 { - s = s[0:3] + s = s[:3] } return "", EscapeError(s) } @@ -224,16 +241,24 @@ func escape(s string, mode encoding) string { // Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/. // A consequence is that it is impossible to tell which slashes in the Path were // slashes in the raw URL and which were %2f. This distinction is rarely important, -// but when it is a client must use other routines to parse the raw URL or construct -// the parsed URL. For example, an HTTP server can consult req.RequestURI, and -// an HTTP client can use URL{Host: "example.com", Opaque: "//example.com/Go%2f"} -// instead of URL{Host: "example.com", Path: "/Go/"}. +// but when it is, code must not use Path directly. +// +// Go 1.5 introduced the RawPath field to hold the encoded form of Path. +// The Parse function sets both Path and RawPath in the URL it returns, +// and URL's String method uses RawPath if it is a valid encoding of Path, +// by calling the EncodedPath method. +// +// In earlier versions of Go, the more indirect workarounds were that an +// HTTP server could consult req.RequestURI and an HTTP client could +// construct a URL struct directly and set the Opaque field instead of Path. +// These still work as well. type URL struct { Scheme string Opaque string // encoded opaque data User *Userinfo // username and password information Host string // host or host:port Path string + RawPath string // encoded path hint (Go 1.5 and later only; see EscapedPath method) RawQuery string // encoded query values, without '?' Fragment string // fragment for references, without '#' } @@ -305,7 +330,7 @@ func getscheme(rawurl string) (scheme, path string, err error) { if i == 0 { return "", "", errors.New("missing protocol scheme") } - return rawurl[0:i], rawurl[i+1:], nil + return rawurl[:i], rawurl[i+1:], nil default: // we have encountered an invalid character, // so there is no valid scheme @@ -324,9 +349,9 @@ func split(s string, c string, cutc bool) (string, string) { return s, "" } if cutc { - return s[0:i], s[i+len(c):] + return s[:i], s[i+len(c):] } - return s[0:i], s[i:] + return s[:i], s[i:] } // Parse parses rawurl into a URL structure. @@ -401,14 +426,17 @@ func parse(rawurl string, viaRequest bool) (url *URL, err error) { if err != nil { goto Error } - if strings.Contains(url.Host, "%") { - err = errors.New("hexadecimal escape in host") - goto Error - } } if url.Path, err = unescape(rest, encodePath); err != nil { goto Error } + // RawPath is a hint as to the encoding of Path to use + // in url.EncodedPath. If that method already gets the + // right answer without RawPath, leave it empty. + // This will help make sure that people don't rely on it in general. + if url.EscapedPath() != rest && validEncodedPath(rest) { + url.RawPath = rest + } return url, nil Error: @@ -418,36 +446,157 @@ Error: func parseAuthority(authority string) (user *Userinfo, host string, err error) { i := strings.LastIndex(authority, "@") if i < 0 { - host = authority - return + host, err = parseHost(authority) + } else { + host, err = parseHost(authority[i+1:]) } - userinfo, host := authority[:i], authority[i+1:] + if err != nil { + return nil, "", err + } + if i < 0 { + return nil, host, nil + } + userinfo := authority[:i] if strings.Index(userinfo, ":") < 0 { if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil { - return + return nil, "", err } user = User(userinfo) } else { username, password := split(userinfo, ":", true) if username, err = unescape(username, encodeUserPassword); err != nil { - return + return nil, "", err } if password, err = unescape(password, encodeUserPassword); err != nil { - return + return nil, "", err } user = UserPassword(username, password) } - return + return user, host, nil +} + +// parseHost parses host as an authority without user +// information. That is, as host[:port]. +func parseHost(host string) (string, error) { + litOrName := host + if strings.HasPrefix(host, "[") { + // Parse an IP-Literal in RFC 3986 and RFC 6874. + // E.g., "[fe80::1], "[fe80::1%25en0]" + // + // RFC 4007 defines "%" as a delimiter character in + // the textual representation of IPv6 addresses. + // Per RFC 6874, in URIs that "%" is encoded as "%25". + i := strings.LastIndex(host, "]") + if i < 0 { + return "", errors.New("missing ']' in host") + } + colonPort := host[i+1:] + if !validOptionalPort(colonPort) { + return "", fmt.Errorf("invalid port %q after host", colonPort) + } + // Parse a host subcomponent without a ZoneID in RFC + // 6874 because the ZoneID is allowed to use the + // percent encoded form. + j := strings.Index(host[:i], "%25") + if j < 0 { + litOrName = host[1:i] + } else { + litOrName = host[1:j] + } + } + + // A URI containing an IP-Literal without a ZoneID or + // IPv4address in RFC 3986 and RFC 6847 must not be + // percent-encoded. + // + // A URI containing a DNS registered name in RFC 3986 is + // allowed to be percent-encoded, though we don't use it for + // now to avoid messing up with the gap between allowed + // characters in URI and allowed characters in DNS. + // See golang.org/issue/7991. + if strings.Contains(litOrName, "%") { + return "", errors.New("percent-encoded characters in host") + } + var err error + if host, err = unescape(host, encodeHost); err != nil { + return "", err + } + return host, nil +} + +// EscapedPath returns the escaped form of u.Path. +// In general there are multiple possible escaped forms of any path. +// EscapedPath returns u.RawPath when it is a valid escaping of u.Path. +// Otherwise EscapedPath ignores u.RawPath and computes an escaped +// form on its own. +// The String and RequestURI methods use EscapedPath to construct +// their results. +// In general, code should call EscapedPath instead of +// reading u.RawPath directly. +func (u *URL) EscapedPath() string { + if u.RawPath != "" && validEncodedPath(u.RawPath) { + p, err := unescape(u.RawPath, encodePath) + if err == nil && p == u.Path { + return u.RawPath + } + } + if u.Path == "*" { + return "*" // don't escape (Issue 11202) + } + return escape(u.Path, encodePath) +} + +// validEncodedPath reports whether s is a valid encoded path. +// It must not contain any bytes that require escaping during path encoding. +func validEncodedPath(s string) bool { + for i := 0; i < len(s); i++ { + // RFC 3986, Appendix A. + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@". + // shouldEscape is not quite compliant with the RFC, + // so we check the sub-delims ourselves and let + // shouldEscape handle the others. + switch s[i] { + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@': + // ok + case '[', ']': + // ok - not specified in RFC 3986 but left alone by modern browsers + case '%': + // ok - percent encoded, will decode + default: + if shouldEscape(s[i], encodePath) { + return false + } + } + } + return true +} + +// validOptionalPort reports whether port is either an empty string +// or matches /^:\d+$/ +func validOptionalPort(port string) bool { + if port == "" { + return true + } + if port[0] != ':' || len(port) == 1 { + return false + } + for _, b := range port[1:] { + if b < '0' || b > '9' { + return false + } + } + return true } // String reassembles the URL into a valid URL string. // The general form of the result is one of: // -// scheme:opaque +// scheme:opaque?query#fragment // scheme://userinfo@host/path?query#fragment // // If u.Opaque is non-empty, String uses the first form; // otherwise it uses the second form. +// To obtain the path, String uses u.EncodedPath(). // // In the second form, the following rules apply: // - if u.Scheme is empty, scheme: is omitted. @@ -475,13 +624,14 @@ func (u *URL) String() string { buf.WriteByte('@') } if h := u.Host; h != "" { - buf.WriteString(h) + buf.WriteString(escape(h, encodeHost)) } } - if u.Path != "" && u.Path[0] != '/' && u.Host != "" { + path := u.EscapedPath() + if path != "" && path[0] != '/' && u.Host != "" { buf.WriteByte('/') } - buf.WriteString(escape(u.Path, encodePath)) + buf.WriteString(path) } if u.RawQuery != "" { buf.WriteByte('?') @@ -639,7 +789,7 @@ func resolvePath(base, ref string) string { return "/" + strings.TrimLeft(strings.Join(dst, "/"), "/") } -// IsAbs returns true if the URL is absolute. +// IsAbs reports whether the URL is absolute. func (u *URL) IsAbs() bool { return u.Scheme != "" } @@ -703,7 +853,7 @@ func (u *URL) Query() Values { func (u *URL) RequestURI() string { result := u.Opaque if result == "" { - result = escape(u.Path, encodePath) + result = u.EscapedPath() if result == "" { result = "/" } diff --git a/libgo/go/net/url/url_test.go b/libgo/go/net/url/url_test.go index d8b19d805d0..ff6e9e4541a 100644 --- a/libgo/go/net/url/url_test.go +++ b/libgo/go/net/url/url_test.go @@ -13,7 +13,7 @@ import ( type URLTest struct { in string - out *URL + out *URL // expected parse; RawPath="" means same as Path roundtrip string // expected result of reserializing the URL; empty means same as "in". } @@ -41,11 +41,12 @@ var urltests = []URLTest{ { "http://www.google.com/file%20one%26two", &URL{ - Scheme: "http", - Host: "www.google.com", - Path: "/file one&two", + Scheme: "http", + Host: "www.google.com", + Path: "/file one&two", + RawPath: "/file%20one%26two", }, - "http://www.google.com/file%20one&two", + "", }, // user { @@ -289,6 +290,140 @@ var urltests = []URLTest{ }, "", }, + // host subcomponent; IPv4 address in RFC 3986 + { + "http://192.168.0.1/", + &URL{ + Scheme: "http", + Host: "192.168.0.1", + Path: "/", + }, + "", + }, + // host and port subcomponents; IPv4 address in RFC 3986 + { + "http://192.168.0.1:8080/", + &URL{ + Scheme: "http", + Host: "192.168.0.1:8080", + Path: "/", + }, + "", + }, + // host subcomponent; IPv6 address in RFC 3986 + { + "http://[fe80::1]/", + &URL{ + Scheme: "http", + Host: "[fe80::1]", + Path: "/", + }, + "", + }, + // host and port subcomponents; IPv6 address in RFC 3986 + { + "http://[fe80::1]:8080/", + &URL{ + Scheme: "http", + Host: "[fe80::1]:8080", + Path: "/", + }, + "", + }, + // host subcomponent; IPv6 address with zone identifier in RFC 6847 + { + "http://[fe80::1%25en0]/", // alphanum zone identifier + &URL{ + Scheme: "http", + Host: "[fe80::1%en0]", + Path: "/", + }, + "", + }, + // host and port subcomponents; IPv6 address with zone identifier in RFC 6847 + { + "http://[fe80::1%25en0]:8080/", // alphanum zone identifier + &URL{ + Scheme: "http", + Host: "[fe80::1%en0]:8080", + Path: "/", + }, + "", + }, + // host subcomponent; IPv6 address with zone identifier in RFC 6847 + { + "http://[fe80::1%25%65%6e%301-._~]/", // percent-encoded+unreserved zone identifier + &URL{ + Scheme: "http", + Host: "[fe80::1%en01-._~]", + Path: "/", + }, + "http://[fe80::1%25en01-._~]/", + }, + // host and port subcomponents; IPv6 address with zone identifier in RFC 6847 + { + "http://[fe80::1%25%65%6e%301-._~]:8080/", // percent-encoded+unreserved zone identifier + &URL{ + Scheme: "http", + Host: "[fe80::1%en01-._~]:8080", + Path: "/", + }, + "http://[fe80::1%25en01-._~]:8080/", + }, + // alternate escapings of path survive round trip + { + "http://rest.rsc.io/foo%2fbar/baz%2Fquux?alt=media", + &URL{ + Scheme: "http", + Host: "rest.rsc.io", + Path: "/foo/bar/baz/quux", + RawPath: "/foo%2fbar/baz%2Fquux", + RawQuery: "alt=media", + }, + "", + }, + // issue 12036 + { + "mysql://a,b,c/bar", + &URL{ + Scheme: "mysql", + Host: "a,b,c", + Path: "/bar", + }, + "", + }, + // worst case host, still round trips + { + "scheme://!$&'()*+,;=hello!:port/path", + &URL{ + Scheme: "scheme", + Host: "!$&'()*+,;=hello!:port", + Path: "/path", + }, + "", + }, + // worst case path, still round trips + { + "http://host/!$&'()*+,;=:@[hello]", + &URL{ + Scheme: "http", + Host: "host", + Path: "/!$&'()*+,;=:@[hello]", + RawPath: "/!$&'()*+,;=:@[hello]", + }, + "", + }, + // golang.org/issue/5684 + { + "http://example.com/oid/[order_id]", + &URL{ + Scheme: "http", + Host: "example.com", + Path: "/oid/[order_id]", + RawPath: "/oid/[order_id]", + }, + "", + }, } // more useful string for debugging than fmt's struct printer @@ -300,8 +435,8 @@ func ufmt(u *URL) string { pass = p } } - return fmt.Sprintf("opaque=%q, scheme=%q, user=%#v, pass=%#v, host=%q, path=%q, rawq=%q, frag=%q", - u.Opaque, u.Scheme, user, pass, u.Host, u.Path, u.RawQuery, u.Fragment) + return fmt.Sprintf("opaque=%q, scheme=%q, user=%#v, pass=%#v, host=%q, path=%q, rawpath=%q, rawq=%q, frag=%q", + u.Opaque, u.Scheme, user, pass, u.Host, u.Path, u.RawPath, u.RawQuery, u.Fragment) } func DoTest(t *testing.T, parse func(string) (*URL, error), name string, tests []URLTest) { @@ -358,9 +493,33 @@ var parseRequestURLTests = []struct { {"/", true}, {pathThatLooksSchemeRelative, true}, {"//not.a.user@%66%6f%6f.com/just/a/path/also", true}, + {"*", true}, + {"http://192.168.0.1/", true}, + {"http://192.168.0.1:8080/", true}, + {"http://[fe80::1]/", true}, + {"http://[fe80::1]:8080/", true}, + + // Tests exercising RFC 6874 compliance: + {"http://[fe80::1%25en0]/", true}, // with alphanum zone identifier + {"http://[fe80::1%25en0]:8080/", true}, // with alphanum zone identifier + {"http://[fe80::1%25%65%6e%301-._~]/", true}, // with percent-encoded+unreserved zone identifier + {"http://[fe80::1%25%65%6e%301-._~]:8080/", true}, // with percent-encoded+unreserved zone identifier + {"foo.html", false}, {"../dir/", false}, - {"*", true}, + {"http://192.168.0.%31/", false}, + {"http://192.168.0.%31:8080/", false}, + {"http://[fe80::%31]/", false}, + {"http://[fe80::%31]:8080/", false}, + {"http://[fe80::%31%25en0]/", false}, + {"http://[fe80::%31%25en0]:8080/", false}, + + // These two cases are valid as textual representations as + // described in RFC 4007, but are not valid as address + // literals with IPv6 zone identifiers in URIs as described in + // RFC 6874. + {"http://[fe80::1%en0]/", false}, + {"http://[fe80::1%en0]:8080/", false}, } func TestParseRequestURI(t *testing.T) { @@ -869,6 +1028,25 @@ var requritests = []RequestURITest{ }, "http://other.example.com/%2F/%2F/", }, + // better fix for issue 4860 + { + &URL{ + Scheme: "http", + Host: "example.com", + Path: "/////", + RawPath: "/%2F/%2F/", + }, + "/%2F/%2F/", + }, + { + &URL{ + Scheme: "http", + Host: "example.com", + Path: "/////", + RawPath: "/WRONG/", // ignored because doesn't match Path + }, + "/////", + }, { &URL{ Scheme: "http", @@ -880,6 +1058,26 @@ var requritests = []RequestURITest{ }, { &URL{ + Scheme: "http", + Host: "example.com", + Path: "/a b", + RawPath: "/a b", // ignored because invalid + RawQuery: "q=go+language", + }, + "/a%20b?q=go+language", + }, + { + &URL{ + Scheme: "http", + Host: "example.com", + Path: "/a?b", + RawPath: "/a?b", // ignored because invalid + RawQuery: "q=go+language", + }, + "/a%3Fb?q=go+language", + }, + { + &URL{ Scheme: "myschema", Opaque: "opaque", }, @@ -914,6 +1112,54 @@ func TestParseFailure(t *testing.T) { } } +func TestParseAuthority(t *testing.T) { + tests := []struct { + in string + wantErr bool + }{ + {"http://[::1]", false}, + {"http://[::1]:80", false}, + {"http://[::1]:namedport", true}, // rfc3986 3.2.3 + {"http://[::1]/", false}, + {"http://[::1]a", true}, + {"http://[::1]%23", true}, + {"http://[::1%25en0]", false}, // valid zone id + {"http://[::1]:", true}, // colon, but no port + {"http://[::1]:%38%30", true}, // no hex in port + {"http://[::1%25%10]", false}, // TODO: reject the %10 after the valid zone %25 separator? + {"http://[%10::1]", true}, // no %xx escapes in IP address + {"http://[::1]/%48", false}, // %xx in path is fine + {"http://%41:8080/", true}, // TODO: arguably we should accept reg-name with %xx + {"mysql://x@y(z:123)/foo", false}, // golang.org/issue/12023 + {"mysql://x@y(1.2.3.4:123)/foo", false}, + {"mysql://x@y([2001:db8::1]:123)/foo", false}, + {"http://[]%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a/", true}, // golang.org/issue/11208 + } + for _, tt := range tests { + u, err := Parse(tt.in) + if tt.wantErr { + if err == nil { + t.Errorf("Parse(%q) = %#v; want an error", tt.in, u) + } + continue + } + if err != nil { + t.Logf("Parse(%q) = %v; want no error", tt.in, err) + } + } +} + +// Issue 11202 +func TestStarRequest(t *testing.T) { + u, err := Parse("*") + if err != nil { + t.Fatal(err) + } + if got, want := u.RequestURI(), "*"; got != want { + t.Errorf("RequestURI = %q; want %q", got, want) + } +} + type shouldEscapeTest struct { in byte mode encoding @@ -926,6 +1172,7 @@ var shouldEscapeTests = []shouldEscapeTest{ {'a', encodeUserPassword, false}, {'a', encodeQueryComponent, false}, {'a', encodeFragment, false}, + {'a', encodeHost, false}, {'z', encodePath, false}, {'A', encodePath, false}, {'Z', encodePath, false}, @@ -950,6 +1197,29 @@ var shouldEscapeTests = []shouldEscapeTest{ {',', encodeUserPassword, false}, {';', encodeUserPassword, false}, {'=', encodeUserPassword, false}, + + // Host (IP address, IPv6 address, registered name, port suffix; §3.2.2) + {'!', encodeHost, false}, + {'$', encodeHost, false}, + {'&', encodeHost, false}, + {'\'', encodeHost, false}, + {'(', encodeHost, false}, + {')', encodeHost, false}, + {'*', encodeHost, false}, + {'+', encodeHost, false}, + {',', encodeHost, false}, + {';', encodeHost, false}, + {'=', encodeHost, false}, + {':', encodeHost, false}, + {'[', encodeHost, false}, + {']', encodeHost, false}, + {'0', encodeHost, false}, + {'9', encodeHost, false}, + {'A', encodeHost, false}, + {'z', encodeHost, false}, + {'_', encodeHost, false}, + {'-', encodeHost, false}, + {'.', encodeHost, false}, } func TestShouldEscape(t *testing.T) { diff --git a/libgo/go/net/z_last_test.go b/libgo/go/net/z_last_test.go deleted file mode 100644 index 716c103db26..00000000000 --- a/libgo/go/net/z_last_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package net - -import ( - "flag" - "fmt" - "testing" - "time" -) - -var testDNSFlood = flag.Bool("dnsflood", false, "whether to test dns query flooding") - -func TestDNSThreadLimit(t *testing.T) { - if !*testDNSFlood { - t.Skip("test disabled; use -dnsflood to enable") - } - - const N = 10000 - c := make(chan int, N) - for i := 0; i < N; i++ { - go func(i int) { - LookupIP(fmt.Sprintf("%d.net-test.golang.org", i)) - c <- 1 - }(i) - } - // Don't bother waiting for the stragglers; stop at 0.9 N. - for i := 0; i < N*9/10; i++ { - if i%100 == 0 { - //println("TestDNSThreadLimit:", i) - } - <-c - } - - // If we're still here, it worked. -} - -func TestLookupIPDeadline(t *testing.T) { - if !*testDNSFlood { - t.Skip("test disabled; use -dnsflood to enable") - } - - const N = 5000 - const timeout = 3 * time.Second - c := make(chan error, 2*N) - for i := 0; i < N; i++ { - name := fmt.Sprintf("%d.net-test.golang.org", i) - go func() { - _, err := lookupIPDeadline(name, time.Now().Add(timeout/2)) - c <- err - }() - go func() { - _, err := lookupIPDeadline(name, time.Now().Add(timeout)) - c <- err - }() - } - qstats := struct { - succeeded, failed int - timeout, temporary, other int - unknown int - }{} - deadline := time.After(timeout + time.Second) - for i := 0; i < 2*N; i++ { - select { - case <-deadline: - t.Fatal("deadline exceeded") - case err := <-c: - switch err := err.(type) { - case nil: - qstats.succeeded++ - case Error: - qstats.failed++ - if err.Timeout() { - qstats.timeout++ - } - if err.Temporary() { - qstats.temporary++ - } - if !err.Timeout() && !err.Temporary() { - qstats.other++ - } - default: - qstats.failed++ - qstats.unknown++ - } - } - } - - // A high volume of DNS queries for sub-domain of golang.org - // would be coordinated by authoritative or recursive server, - // or stub resolver which implements query-response rate - // limitation, so we can expect some query successes and more - // failures including timeout, temporary and other here. - // As a rule, unknown must not be shown but it might possibly - // happen due to issue 4856 for now. - t.Logf("%v succeeded, %v failed (%v timeout, %v temporary, %v other, %v unknown)", qstats.succeeded, qstats.failed, qstats.timeout, qstats.temporary, qstats.other, qstats.unknown) -} |