summaryrefslogtreecommitdiff
path: root/libgo/go/net
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/net')
-rw-r--r--libgo/go/net/addrselect.go388
-rw-r--r--libgo/go/net/addrselect_test.go219
-rw-r--r--libgo/go/net/cgo_android.go4
-rw-r--r--libgo/go/net/cgo_bsd.go8
-rw-r--r--libgo/go/net/cgo_linux.go16
-rw-r--r--libgo/go/net/cgo_netbsd.go6
-rw-r--r--libgo/go/net/cgo_openbsd.go4
-rw-r--r--libgo/go/net/cgo_resnew.go36
-rw-r--r--libgo/go/net/cgo_resold.go36
-rw-r--r--libgo/go/net/cgo_socknew.go32
-rw-r--r--libgo/go/net/cgo_sockold.go32
-rw-r--r--libgo/go/net/cgo_solaris.go16
-rw-r--r--libgo/go/net/cgo_stub.go16
-rw-r--r--libgo/go/net/cgo_unix.go182
-rw-r--r--libgo/go/net/cgo_unix_test.go6
-rw-r--r--libgo/go/net/cgo_windows.go13
-rw-r--r--libgo/go/net/conf.go308
-rw-r--r--libgo/go/net/conf_netcgo.go17
-rw-r--r--libgo/go/net/conf_test.go301
-rw-r--r--libgo/go/net/conn_test.go107
-rw-r--r--libgo/go/net/dial.go279
-rw-r--r--libgo/go/net/dial_gen.go20
-rw-r--r--libgo/go/net/dial_test.go867
-rw-r--r--libgo/go/net/dialgoogle_test.go209
-rw-r--r--libgo/go/net/dnsclient.go67
-rw-r--r--libgo/go/net/dnsclient_test.go2
-rw-r--r--libgo/go/net/dnsclient_unix.go403
-rw-r--r--libgo/go/net/dnsclient_unix_test.go375
-rw-r--r--libgo/go/net/dnsconfig_unix.go54
-rw-r--r--libgo/go/net/dnsconfig_unix_test.go63
-rw-r--r--libgo/go/net/dnsmsg.go18
-rw-r--r--libgo/go/net/dnsmsg_test.go175
-rw-r--r--libgo/go/net/dnsname_test.go31
-rw-r--r--libgo/go/net/error_plan9_test.go17
-rw-r--r--libgo/go/net/error_posix_test.go44
-rw-r--r--libgo/go/net/error_test.go673
-rw-r--r--libgo/go/net/external_test.go167
-rw-r--r--libgo/go/net/fd_plan9.go18
-rw-r--r--libgo/go/net/fd_poll_nacl.go9
-rw-r--r--libgo/go/net/fd_poll_runtime.go15
-rw-r--r--libgo/go/net/fd_posix.go21
-rw-r--r--libgo/go/net/fd_posix_test.go (renamed from libgo/go/net/fd_unix_test.go)13
-rw-r--r--libgo/go/net/fd_unix.go126
-rw-r--r--libgo/go/net/fd_windows.go114
-rw-r--r--libgo/go/net/file.go48
-rw-r--r--libgo/go/net/file_plan9.go26
-rw-r--r--libgo/go/net/file_stub.go28
-rw-r--r--libgo/go/net/file_test.go130
-rw-r--r--libgo/go/net/file_unix.go88
-rw-r--r--libgo/go/net/file_windows.go24
-rw-r--r--libgo/go/net/hook.go12
-rw-r--r--libgo/go/net/hook_cloexec.go14
-rw-r--r--libgo/go/net/hook_plan9.go9
-rw-r--r--libgo/go/net/hook_unix.go21
-rw-r--r--libgo/go/net/hook_windows.go21
-rw-r--r--libgo/go/net/hosts.go35
-rw-r--r--libgo/go/net/hosts_test.go151
-rw-r--r--libgo/go/net/http/cgi/child.go6
-rw-r--r--libgo/go/net/http/cgi/child_test.go21
-rw-r--r--libgo/go/net/http/cgi/host.go10
-rw-r--r--libgo/go/net/http/cgi/host_test.go43
-rw-r--r--libgo/go/net/http/cgi/matryoshka_test.go15
-rw-r--r--libgo/go/net/http/cgi/testdata/test.cgi2
-rw-r--r--libgo/go/net/http/client.go82
-rw-r--r--libgo/go/net/http/client_test.go126
-rw-r--r--libgo/go/net/http/cookie.go36
-rw-r--r--libgo/go/net/http/cookie_test.go18
-rw-r--r--libgo/go/net/http/example_test.go23
-rw-r--r--libgo/go/net/http/export_test.go24
-rw-r--r--libgo/go/net/http/fcgi/child.go25
-rw-r--r--libgo/go/net/http/fcgi/fcgi_test.go106
-rw-r--r--libgo/go/net/http/fs.go53
-rw-r--r--libgo/go/net/http/fs_test.go51
-rw-r--r--libgo/go/net/http/header.go2
-rw-r--r--libgo/go/net/http/http_test.go58
-rw-r--r--libgo/go/net/http/httptest/server.go42
-rw-r--r--libgo/go/net/http/httputil/dump.go17
-rw-r--r--libgo/go/net/http/httputil/dump_test.go8
-rw-r--r--libgo/go/net/http/httputil/reverseproxy.go67
-rw-r--r--libgo/go/net/http/httputil/reverseproxy_test.go70
-rw-r--r--libgo/go/net/http/internal/chunked.go17
-rw-r--r--libgo/go/net/http/lex.go73
-rw-r--r--libgo/go/net/http/lex_test.go70
-rw-r--r--libgo/go/net/http/main_test.go26
-rw-r--r--libgo/go/net/http/npn_test.go22
-rw-r--r--libgo/go/net/http/pprof/pprof.go40
-rw-r--r--libgo/go/net/http/proxy_test.go2
-rw-r--r--libgo/go/net/http/readrequest_test.go88
-rw-r--r--libgo/go/net/http/request.go139
-rw-r--r--libgo/go/net/http/request_test.go109
-rw-r--r--libgo/go/net/http/requestwrite_test.go69
-rw-r--r--libgo/go/net/http/response.go15
-rw-r--r--libgo/go/net/http/response_test.go51
-rw-r--r--libgo/go/net/http/responsewrite_test.go15
-rw-r--r--libgo/go/net/http/serve_test.go654
-rw-r--r--libgo/go/net/http/server.go279
-rw-r--r--libgo/go/net/http/sniff.go10
-rw-r--r--libgo/go/net/http/transfer.go144
-rw-r--r--libgo/go/net/http/transport.go360
-rw-r--r--libgo/go/net/http/transport_test.go528
-rw-r--r--libgo/go/net/interface.go44
-rw-r--r--libgo/go/net/interface_bsd.go93
-rw-r--r--libgo/go/net/interface_darwin.go43
-rw-r--r--libgo/go/net/interface_freebsd.go43
-rw-r--r--libgo/go/net/interface_linux.go34
-rw-r--r--libgo/go/net/interface_test.go200
-rw-r--r--libgo/go/net/interface_windows.go250
-rw-r--r--libgo/go/net/internal/socktest/main_test.go56
-rw-r--r--libgo/go/net/internal/socktest/main_unix_test.go24
-rw-r--r--libgo/go/net/internal/socktest/main_windows_test.go22
-rw-r--r--libgo/go/net/internal/socktest/switch.go169
-rw-r--r--libgo/go/net/internal/socktest/switch_posix.go58
-rw-r--r--libgo/go/net/internal/socktest/switch_stub.go16
-rw-r--r--libgo/go/net/internal/socktest/switch_unix.go29
-rw-r--r--libgo/go/net/internal/socktest/switch_windows.go29
-rw-r--r--libgo/go/net/internal/socktest/sys_cloexec.go42
-rw-r--r--libgo/go/net/internal/socktest/sys_unix.go193
-rw-r--r--libgo/go/net/internal/socktest/sys_windows.go156
-rw-r--r--libgo/go/net/ip.go77
-rw-r--r--libgo/go/net/ip_test.go91
-rw-r--r--libgo/go/net/ipraw_test.go244
-rw-r--r--libgo/go/net/iprawsock.go18
-rw-r--r--libgo/go/net/iprawsock_plan9.go16
-rw-r--r--libgo/go/net/iprawsock_posix.go82
-rw-r--r--libgo/go/net/ipsock.go201
-rw-r--r--libgo/go/net/ipsock_plan9.go67
-rw-r--r--libgo/go/net/ipsock_posix.go43
-rw-r--r--libgo/go/net/ipsock_test.go235
-rw-r--r--libgo/go/net/listen_test.go685
-rw-r--r--libgo/go/net/lookup.go59
-rw-r--r--libgo/go/net/lookup_plan9.go21
-rw-r--r--libgo/go/net/lookup_stub.go2
-rw-r--r--libgo/go/net/lookup_test.go444
-rw-r--r--libgo/go/net/lookup_unix.go151
-rw-r--r--libgo/go/net/lookup_windows.go122
-rw-r--r--libgo/go/net/mac.go6
-rw-r--r--libgo/go/net/mac_test.go19
-rw-r--r--libgo/go/net/mail/example_test.go79
-rw-r--r--libgo/go/net/mail/message.go260
-rw-r--r--libgo/go/net/mail/message_test.go274
-rw-r--r--libgo/go/net/main_cloexec_test.go25
-rw-r--r--libgo/go/net/main_plan9_test.go15
-rw-r--r--libgo/go/net/main_posix_test.go50
-rw-r--r--libgo/go/net/main_test.go204
-rw-r--r--libgo/go/net/main_unix_test.go52
-rw-r--r--libgo/go/net/main_windows_test.go36
-rw-r--r--libgo/go/net/mockicmp_test.go116
-rw-r--r--libgo/go/net/mockserver_test.go464
-rw-r--r--libgo/go/net/multicast_test.go188
-rw-r--r--libgo/go/net/net.go241
-rw-r--r--libgo/go/net/net_test.go403
-rw-r--r--libgo/go/net/non_unix_test.go11
-rw-r--r--libgo/go/net/nss.go159
-rw-r--r--libgo/go/net/nss_test.go169
-rw-r--r--libgo/go/net/packetconn_test.go119
-rw-r--r--libgo/go/net/parse.go207
-rw-r--r--libgo/go/net/parse_test.go36
-rw-r--r--libgo/go/net/pipe.go6
-rw-r--r--libgo/go/net/pipe_test.go25
-rw-r--r--libgo/go/net/platform_test.go159
-rw-r--r--libgo/go/net/port.go2
-rw-r--r--libgo/go/net/port_test.go24
-rw-r--r--libgo/go/net/port_unix.go2
-rw-r--r--libgo/go/net/protoconn_test.go178
-rw-r--r--libgo/go/net/rpc/client_test.go2
-rw-r--r--libgo/go/net/rpc/server.go3
-rw-r--r--libgo/go/net/sendfile_dragonfly.go7
-rw-r--r--libgo/go/net/sendfile_freebsd.go7
-rw-r--r--libgo/go/net/sendfile_linux.go7
-rw-r--r--libgo/go/net/sendfile_solaris.go110
-rw-r--r--libgo/go/net/sendfile_stub.go2
-rw-r--r--libgo/go/net/sendfile_windows.go2
-rw-r--r--libgo/go/net/server_test.go661
-rw-r--r--libgo/go/net/singleflight.go109
-rw-r--r--libgo/go/net/smtp/smtp.go35
-rw-r--r--libgo/go/net/smtp/smtp_test.go44
-rw-r--r--libgo/go/net/sock_cloexec.go39
-rw-r--r--libgo/go/net/sock_posix.go10
-rw-r--r--libgo/go/net/sock_windows.go14
-rw-r--r--libgo/go/net/sockopt_bsd.go2
-rw-r--r--libgo/go/net/sys_cloexec.go21
-rw-r--r--libgo/go/net/tcp_test.go188
-rw-r--r--libgo/go/net/tcpsock.go13
-rw-r--r--libgo/go/net/tcpsock_plan9.go61
-rw-r--r--libgo/go/net/tcpsock_posix.go99
-rw-r--r--libgo/go/net/tcpsockopt_plan9.go3
-rw-r--r--libgo/go/net/tcpsockopt_solaris.go24
-rw-r--r--libgo/go/net/tcpsockopt_unix.go2
-rw-r--r--libgo/go/net/tcpsockopt_windows.go2
-rw-r--r--libgo/go/net/testdata/ipv4-hosts12
-rw-r--r--libgo/go/net/testdata/ipv6-hosts11
-rw-r--r--libgo/go/net/testdata/openbsd-resolv.conf5
-rw-r--r--libgo/go/net/testdata/singleline-hosts (renamed from libgo/go/net/testdata/hosts_singleline)0
-rw-r--r--libgo/go/net/textproto/reader.go51
-rw-r--r--libgo/go/net/textproto/reader_test.go20
-rw-r--r--libgo/go/net/timeout_test.go1237
-rw-r--r--libgo/go/net/udp_test.go330
-rw-r--r--libgo/go/net/udpsock.go13
-rw-r--r--libgo/go/net/udpsock_plan9.go52
-rw-r--r--libgo/go/net/udpsock_posix.go103
-rw-r--r--libgo/go/net/unicast_posix_test.go469
-rw-r--r--libgo/go/net/unix_test.go264
-rw-r--r--libgo/go/net/unixsock.go6
-rw-r--r--libgo/go/net/unixsock_plan9.go38
-rw-r--r--libgo/go/net/unixsock_posix.go108
-rw-r--r--libgo/go/net/url/url.go200
-rw-r--r--libgo/go/net/url/url_test.go286
-rw-r--r--libgo/go/net/z_last_test.go99
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)
-}