Skip to content

Commit 1f54410

Browse files
committed
net: make IP.{String,MarshalText} return helpful information on address error
This change makes String and MarshalText methods of IP return a hexadecial form of IP with no punctuation as part of error notification. It doesn't affect the existing behavior of ParseIP. Also fixes bad shadowing in ipToSockaddr and makes use of reserved IP address blocks for documnetation. Fixes #15052. Updates #15228. Change-Id: I9e9ecce308952ed5683066c3d1bb6a7b36458c65 Reviewed-on: https://go-review.googlesource.com/21642 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent a119f88 commit 1f54410

File tree

4 files changed

+192
-43
lines changed

4 files changed

+192
-43
lines changed

src/net/error_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,55 @@ func TestProtocolDialError(t *testing.T) {
206206
}
207207
}
208208

209+
func TestDialAddrError(t *testing.T) {
210+
switch runtime.GOOS {
211+
case "nacl", "plan9":
212+
t.Skipf("not supported on %s", runtime.GOOS)
213+
}
214+
215+
for _, tt := range []struct {
216+
network string
217+
lit string
218+
addr *TCPAddr
219+
}{
220+
{"tcp4", "::1", nil},
221+
{"tcp4", "", &TCPAddr{IP: IPv6loopback}},
222+
// We don't test the {"tcp6", "byte sequence", nil}
223+
// case for now because there is no easy way to
224+
// control name resolution.
225+
{"tcp6", "", &TCPAddr{IP: IP{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}}},
226+
} {
227+
var err error
228+
var c Conn
229+
if tt.lit != "" {
230+
c, err = Dial(tt.network, JoinHostPort(tt.lit, "0"))
231+
} else {
232+
c, err = DialTCP(tt.network, nil, tt.addr)
233+
}
234+
if err == nil {
235+
c.Close()
236+
t.Errorf("%s %q/%v: should fail", tt.network, tt.lit, tt.addr)
237+
continue
238+
}
239+
if perr := parseDialError(err); perr != nil {
240+
t.Error(perr)
241+
continue
242+
}
243+
aerr, ok := err.(*OpError).Err.(*AddrError)
244+
if !ok {
245+
t.Errorf("%s %q/%v: should be AddrError: %v", tt.network, tt.lit, tt.addr, err)
246+
continue
247+
}
248+
want := tt.lit
249+
if tt.lit == "" {
250+
want = tt.addr.IP.String()
251+
}
252+
if aerr.Addr != want {
253+
t.Fatalf("%s: got %q; want %q", tt.network, aerr.Addr, want)
254+
}
255+
}
256+
}
257+
209258
var listenErrorTests = []struct {
210259
network, address string
211260
}{

src/net/ip.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,11 @@ func (ip IP) Mask(mask IPMask) IP {
252252
}
253253

254254
// String returns the string form of the IP address ip.
255-
// If the address is an IPv4 address, the string representation
256-
// is dotted decimal ("74.125.19.99"). Otherwise the representation
257-
// is IPv6 ("2001:4860:0:2001::68").
255+
// It returns one of 4 forms:
256+
// - "<nil>", if ip has length 0
257+
// - dotted decimal ("192.0.2.1"), if ip is an IPv4 or IP4-mapped IPv6 address
258+
// - IPv6 ("2001:db9::1"), if ip is a valid IPv6 address
259+
// - the hexadecimal form of ip, without punctuation, if no other cases apply
258260
func (ip IP) String() string {
259261
p := ip
260262

@@ -270,7 +272,7 @@ func (ip IP) String() string {
270272
uitoa(uint(p4[3]))
271273
}
272274
if len(p) != IPv6len {
273-
return "?"
275+
return hexString(ip)
274276
}
275277

276278
// Find longest run of zeros.
@@ -312,6 +314,14 @@ func (ip IP) String() string {
312314
return string(b)
313315
}
314316

317+
func hexString(b []byte) string {
318+
s := make([]byte, len(b)*2)
319+
for i, tn := range b {
320+
s[i*2], s[i*2+1] = hexDigit[tn>>4], hexDigit[tn&0xf]
321+
}
322+
return string(s)
323+
}
324+
315325
// ipEmptyString is like ip.String except that it returns
316326
// an empty string when ip is unset.
317327
func ipEmptyString(ip IP) string {
@@ -426,11 +436,7 @@ func (m IPMask) String() string {
426436
if len(m) == 0 {
427437
return "<nil>"
428438
}
429-
buf := make([]byte, len(m)*2)
430-
for i, b := range m {
431-
buf[i*2], buf[i*2+1] = hexDigit[b>>4], hexDigit[b&0xf]
432-
}
433-
return string(buf)
439+
return hexString(m)
434440
}
435441

436442
func networkNumberAndMask(n *IPNet) (ip IP, m IPMask) {

src/net/ip_test.go

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package net
66

77
import (
8+
"bytes"
89
"reflect"
910
"runtime"
1011
"testing"
@@ -124,30 +125,119 @@ func TestMarshalEmptyIP(t *testing.T) {
124125
}
125126

126127
var ipStringTests = []struct {
127-
in IP
128-
out string // see RFC 5952
128+
in IP // see RFC 791 and RFC 4291
129+
str string // see RFC 791, RFC 4291 and RFC 5952
130+
byt []byte
131+
error
129132
}{
130-
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0x1, 0x23, 0, 0x12, 0, 0x1}, "2001:db8::123:12:1"},
131-
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1}, "2001:db8::1"},
132-
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0x1, 0, 0, 0, 0x1, 0, 0, 0, 0x1}, "2001:db8:0:1:0:1:0:1"},
133-
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0x1, 0, 0, 0, 0x1, 0, 0, 0, 0x1, 0, 0}, "2001:db8:1:0:1:0:1:0"},
134-
{IP{0x20, 0x1, 0, 0, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1}, "2001::1:0:0:1"},
135-
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0}, "2001:db8:0:0:1::"},
136-
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1}, "2001:db8::1:0:0:1"},
137-
{IP{0x20, 0x1, 0xD, 0xB8, 0, 0, 0, 0, 0, 0xA, 0, 0xB, 0, 0xC, 0, 0xD}, "2001:db8::a:b:c:d"},
138-
{IPv4(192, 168, 0, 1), "192.168.0.1"},
139-
{nil, ""},
133+
// IPv4 address
134+
{
135+
IP{192, 0, 2, 1},
136+
"192.0.2.1",
137+
[]byte("192.0.2.1"),
138+
nil,
139+
},
140+
{
141+
IP{0, 0, 0, 0},
142+
"0.0.0.0",
143+
[]byte("0.0.0.0"),
144+
nil,
145+
},
146+
147+
// IPv4-mapped IPv6 address
148+
{
149+
IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 0, 2, 1},
150+
"192.0.2.1",
151+
[]byte("192.0.2.1"),
152+
nil,
153+
},
154+
{
155+
IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0},
156+
"0.0.0.0",
157+
[]byte("0.0.0.0"),
158+
nil,
159+
},
160+
161+
// IPv6 address
162+
{
163+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0x1, 0x23, 0, 0x12, 0, 0x1},
164+
"2001:db8::123:12:1",
165+
[]byte("2001:db8::123:12:1"),
166+
nil,
167+
},
168+
{
169+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1},
170+
"2001:db8::1",
171+
[]byte("2001:db8::1"),
172+
nil,
173+
},
174+
{
175+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0x1, 0, 0, 0, 0x1, 0, 0, 0, 0x1},
176+
"2001:db8:0:1:0:1:0:1",
177+
[]byte("2001:db8:0:1:0:1:0:1"),
178+
nil,
179+
},
180+
{
181+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0x1, 0, 0, 0, 0x1, 0, 0, 0, 0x1, 0, 0},
182+
"2001:db8:1:0:1:0:1:0",
183+
[]byte("2001:db8:1:0:1:0:1:0"),
184+
nil,
185+
},
186+
{
187+
IP{0x20, 0x1, 0, 0, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1},
188+
"2001::1:0:0:1",
189+
[]byte("2001::1:0:0:1"),
190+
nil,
191+
},
192+
{
193+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0},
194+
"2001:db8:0:0:1::",
195+
[]byte("2001:db8:0:0:1::"),
196+
nil,
197+
},
198+
{
199+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1},
200+
"2001:db8::1:0:0:1",
201+
[]byte("2001:db8::1:0:0:1"),
202+
nil,
203+
},
204+
{
205+
IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0xa, 0, 0xb, 0, 0xc, 0, 0xd},
206+
"2001:db8::a:b:c:d",
207+
[]byte("2001:db8::a:b:c:d"),
208+
nil,
209+
},
210+
{
211+
IPv6unspecified,
212+
"::",
213+
[]byte("::"),
214+
nil,
215+
},
216+
217+
// IP wildcard equivalent address in Dial/Listen API
218+
{
219+
nil,
220+
"<nil>",
221+
nil,
222+
nil,
223+
},
224+
225+
// Opaque byte sequence
226+
{
227+
IP{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
228+
"0123456789abcdef",
229+
nil,
230+
&AddrError{Err: "invalid IP address", Addr: "0123456789abcdef"},
231+
},
140232
}
141233

142234
func TestIPString(t *testing.T) {
143235
for _, tt := range ipStringTests {
144-
if tt.in != nil {
145-
if out := tt.in.String(); out != tt.out {
146-
t.Errorf("IP.String(%v) = %q, want %q", tt.in, out, tt.out)
147-
}
236+
if out := tt.in.String(); out != tt.str {
237+
t.Errorf("IP.String(%v) = %q, want %q", tt.in, out, tt.str)
148238
}
149-
if out, err := tt.in.MarshalText(); string(out) != tt.out || err != nil {
150-
t.Errorf("IP.MarshalText(%v) = %q, %v, want %q, nil", tt.in, out, err, tt.out)
239+
if out, err := tt.in.MarshalText(); !bytes.Equal(out, tt.byt) || !reflect.DeepEqual(err, tt.error) {
240+
t.Errorf("IP.MarshalText(%v) = %v, %v, want %v, %v", tt.in, out, err, tt.byt, tt.error)
151241
}
152242
}
153243
}

src/net/ipsock_posix.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
66

7-
// Internet protocol family sockets for POSIX
8-
97
package net
108

119
import (
@@ -52,7 +50,7 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {
5250
}{
5351
// IPv6 communication capability
5452
{laddr: TCPAddr{IP: ParseIP("::1")}, value: 1},
55-
// IPv6 IPv4-mapped address communication capability
53+
// IPv4-mapped IPv6 address communication capability
5654
{laddr: TCPAddr{IP: IPv4(127, 0, 0, 1)}, value: 0},
5755
}
5856
var supps [2]bool
@@ -154,8 +152,6 @@ func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family
154152
return syscall.AF_INET6, false
155153
}
156154

157-
// Internet sockets (TCP, UDP, IP)
158-
159155
func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string, cancel <-chan struct{}) (fd *netFD, err error) {
160156
family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
161157
return socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline, cancel)
@@ -167,27 +163,35 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e
167163
if len(ip) == 0 {
168164
ip = IPv4zero
169165
}
170-
if ip = ip.To4(); ip == nil {
166+
ip4 := ip.To4()
167+
if ip4 == nil {
171168
return nil, &AddrError{Err: "non-IPv4 address", Addr: ip.String()}
172169
}
173170
sa := &syscall.SockaddrInet4{Port: port}
174-
copy(sa.Addr[:], ip)
171+
copy(sa.Addr[:], ip4)
175172
return sa, nil
176173
case syscall.AF_INET6:
177-
if len(ip) == 0 {
178-
ip = IPv6zero
179-
}
180-
// IPv4 callers use 0.0.0.0 to mean "announce on any available address".
181-
// In IPv6 mode, Linux treats that as meaning "announce on 0.0.0.0",
182-
// which it refuses to do. Rewrite to the IPv6 unspecified address.
183-
if ip.Equal(IPv4zero) {
174+
// In general, an IP wildcard address, which is either
175+
// "0.0.0.0" or "::", means the entire IP addressing
176+
// space. For some historical reason, it is used to
177+
// specify "any available address" on some operations
178+
// of IP node.
179+
//
180+
// When the IP node supports IPv4-mapped IPv6 address,
181+
// we allow an listener to listen to the wildcard
182+
// address of both IP addressing spaces by specifying
183+
// IPv6 wildcard address.
184+
if len(ip) == 0 || ip.Equal(IPv4zero) {
184185
ip = IPv6zero
185186
}
186-
if ip = ip.To16(); ip == nil {
187+
// We accept any IPv6 address including IPv4-mapped
188+
// IPv6 address.
189+
ip6 := ip.To16()
190+
if ip6 == nil {
187191
return nil, &AddrError{Err: "non-IPv6 address", Addr: ip.String()}
188192
}
189193
sa := &syscall.SockaddrInet6{Port: port, ZoneId: uint32(zoneToInt(zone))}
190-
copy(sa.Addr[:], ip)
194+
copy(sa.Addr[:], ip6)
191195
return sa, nil
192196
}
193197
return nil, &AddrError{Err: "invalid address family", Addr: ip.String()}

0 commit comments

Comments
 (0)