From 2e71489ed1a30f95d0e11685536f5d61d9e858b3 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 31 Jan 2023 09:00:22 +0000 Subject: [PATCH] net: use net/netip for address parsing Fixes #58098 Change-Id: I6ef963ba2c110215b8eda763cbfb258a25c8199a GitHub-Last-Rev: 3d22f076b725337fa97eedcf92a1520a72bf0c17 GitHub-Pull-Request: golang/go#58143 Reviewed-on: https://go-review.googlesource.com/c/go/+/463987 TryBot-Result: Gopher Robot Reviewed-by: Damien Neil Run-TryBot: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor --- src/net/cgo_unix.go | 11 +- src/net/dnsconfig_unix.go | 5 +- src/net/hosts.go | 15 +-- src/net/ip.go | 267 ++++---------------------------------- src/net/ip_test.go | 16 +++ src/net/lookup.go | 8 +- 6 files changed, 51 insertions(+), 271 deletions(-) diff --git a/src/net/cgo_unix.go b/src/net/cgo_unix.go index 8e43bad8b6..6e43e8271a 100644 --- a/src/net/cgo_unix.go +++ b/src/net/cgo_unix.go @@ -14,6 +14,7 @@ package net import ( "context" "errors" + "net/netip" "syscall" "unsafe" @@ -240,15 +241,11 @@ const ( ) func cgoLookupPTR(ctx context.Context, addr string) (names []string, err error, completed bool) { - var zone string - ip := parseIPv4(addr) - if ip == nil { - ip, zone = parseIPv6Zone(addr) - } - if ip == nil { + ip, err := netip.ParseAddr(addr) + if err != nil { return nil, &DNSError{Err: "invalid address", Name: addr}, true } - sa, salen := cgoSockaddr(ip, zone) + sa, salen := cgoSockaddr(IP(ip.AsSlice()), ip.Zone()) if sa == nil { return nil, &DNSError{Err: "invalid address " + ip.String(), Name: addr}, true } diff --git a/src/net/dnsconfig_unix.go b/src/net/dnsconfig_unix.go index 8f6ae34c1b..69b300410a 100644 --- a/src/net/dnsconfig_unix.go +++ b/src/net/dnsconfig_unix.go @@ -10,6 +10,7 @@ package net import ( "internal/bytealg" + "net/netip" "time" ) @@ -51,9 +52,7 @@ func dnsReadConfig(filename string) *dnsConfig { // One more check: make sure server name is // just an IP address. Otherwise we need DNS // to look it up. - if parseIPv4(f[1]) != nil { - conf.servers = append(conf.servers, JoinHostPort(f[1], "53")) - } else if ip, _ := parseIPv6Zone(f[1]); ip != nil { + if _, err := netip.ParseAddr(f[1]); err == nil { conf.servers = append(conf.servers, JoinHostPort(f[1], "53")) } } diff --git a/src/net/hosts.go b/src/net/hosts.go index dbf8fea136..8b954188bf 100644 --- a/src/net/hosts.go +++ b/src/net/hosts.go @@ -6,6 +6,7 @@ package net import ( "internal/bytealg" + "net/netip" "sync" "time" ) @@ -13,19 +14,11 @@ import ( const cacheMaxAge = 5 * time.Second func parseLiteralIP(addr string) string { - var ip IP - var zone string - ip = parseIPv4(addr) - if ip == nil { - ip, zone = parseIPv6Zone(addr) - } - if ip == nil { + ip, err := netip.ParseAddr(addr) + if err != nil { return "" } - if zone == "" { - return ip.String() - } - return ip.String() + "%" + zone + return ip.String() } type byName struct { diff --git a/src/net/ip.go b/src/net/ip.go index d9f3da7021..d51ba10eec 100644 --- a/src/net/ip.go +++ b/src/net/ip.go @@ -15,6 +15,7 @@ package net import ( "internal/bytealg" "internal/itoa" + "net/netip" ) // IP address lengths (bytes). @@ -285,25 +286,6 @@ func (ip IP) Mask(mask IPMask) IP { return out } -// ubtoa encodes the string form of the integer v to dst[start:] and -// returns the number of bytes written to dst. The caller must ensure -// that dst has sufficient length. -func ubtoa(dst []byte, start int, v byte) int { - if v < 10 { - dst[start] = v + '0' - return 1 - } else if v < 100 { - dst[start+1] = v%10 + '0' - dst[start] = v/10 + '0' - return 2 - } - - dst[start+2] = v%10 + '0' - dst[start+1] = (v/10)%10 + '0' - dst[start] = v/100 + '0' - return 3 -} - // String returns the string form of the IP address ip. // It returns one of 4 forms: // - "", if ip has length 0 @@ -311,73 +293,18 @@ func ubtoa(dst []byte, start int, v byte) int { // - IPv6 conforming to RFC 5952 ("2001:db8::1"), if ip is a valid IPv6 address // - the hexadecimal form of ip, without punctuation, if no other cases apply func (ip IP) String() string { - p := ip - if len(ip) == 0 { return "" } - // If IPv4, use dotted notation. - if p4 := p.To4(); len(p4) == IPv4len { - const maxIPv4StringLen = len("255.255.255.255") - b := make([]byte, maxIPv4StringLen) - - n := ubtoa(b, 0, p4[0]) - b[n] = '.' - n++ - - n += ubtoa(b, n, p4[1]) - b[n] = '.' - n++ - - n += ubtoa(b, n, p4[2]) - b[n] = '.' - n++ - - n += ubtoa(b, n, p4[3]) - return string(b[:n]) - } - if len(p) != IPv6len { + if len(ip) != IPv4len && len(ip) != IPv6len { return "?" + hexString(ip) } - - // Find longest run of zeros. - e0 := -1 - e1 := -1 - for i := 0; i < IPv6len; i += 2 { - j := i - for j < IPv6len && p[j] == 0 && p[j+1] == 0 { - j += 2 - } - if j > i && j-i > e1-e0 { - e0 = i - e1 = j - i = j - } - } - // The symbol "::" MUST NOT be used to shorten just one 16 bit 0 field. - if e1-e0 <= 2 { - e0 = -1 - e1 = -1 - } - - const maxLen = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") - b := make([]byte, 0, maxLen) - - // Print with possible :: in place of run of zeros - for i := 0; i < IPv6len; i += 2 { - if i == e0 { - b = append(b, ':', ':') - i = e1 - if i >= IPv6len { - break - } - } else if i > 0 { - b = append(b, ':') - } - b = appendHex(b, (uint32(p[i])<<8)|uint32(p[i+1])) + // If IPv4, use dotted notation. + if p4 := ip.To4(); len(p4) == IPv4len { + return netip.AddrFrom4([4]byte(p4)).String() } - return string(b) + return netip.AddrFrom16([16]byte(ip)).String() } func hexString(b []byte) string { @@ -559,175 +486,24 @@ func (n *IPNet) String() string { return nn.String() + "/" + itoa.Uitoa(uint(l)) } -// Parse IPv4 address (d.d.d.d). -func parseIPv4(s string) IP { - var p [IPv4len]byte - for i := 0; i < IPv4len; i++ { - if len(s) == 0 { - // Missing octets. - return nil - } - if i > 0 { - if s[0] != '.' { - return nil - } - s = s[1:] - } - n, c, ok := dtoi(s) - if !ok || n > 0xFF { - return nil - } - if c > 1 && s[0] == '0' { - // Reject non-zero components with leading zeroes. - return nil - } - s = s[c:] - p[i] = byte(n) - } - if len(s) != 0 { - return nil - } - return IPv4(p[0], p[1], p[2], p[3]) -} - -// parseIPv6Zone parses s as a literal IPv6 address and its associated zone -// identifier which is described in RFC 4007. -func parseIPv6Zone(s string) (IP, string) { - s, zone := splitHostZone(s) - return parseIPv6(s), zone -} - -// parseIPv6 parses s as a literal IPv6 address described in RFC 4291 -// and RFC 5952. -func parseIPv6(s string) (ip IP) { - ip = make(IP, IPv6len) - ellipsis := -1 // position of ellipsis in ip - - // Might have leading ellipsis - if len(s) >= 2 && s[0] == ':' && s[1] == ':' { - ellipsis = 0 - s = s[2:] - // Might be only ellipsis - if len(s) == 0 { - return ip - } - } - - // Loop, parsing hex numbers followed by colon. - i := 0 - for i < IPv6len { - // Hex number. - n, c, ok := xtoi(s) - if !ok || n > 0xFFFF { - return nil - } - - // If followed by dot, might be in trailing IPv4. - if c < len(s) && s[c] == '.' { - if ellipsis < 0 && i != IPv6len-IPv4len { - // Not the right place. - return nil - } - if i+IPv4len > IPv6len { - // Not enough room. - return nil - } - ip4 := parseIPv4(s) - if ip4 == nil { - return nil - } - ip[i] = ip4[12] - ip[i+1] = ip4[13] - ip[i+2] = ip4[14] - ip[i+3] = ip4[15] - s = "" - i += IPv4len - break - } - - // Save this 16-bit chunk. - ip[i] = byte(n >> 8) - ip[i+1] = byte(n) - i += 2 - - // Stop at end of string. - s = s[c:] - if len(s) == 0 { - break - } - - // Otherwise must be followed by colon and more. - if s[0] != ':' || len(s) == 1 { - return nil - } - s = s[1:] - - // Look for ellipsis. - if s[0] == ':' { - if ellipsis >= 0 { // already have one - return nil - } - ellipsis = i - s = s[1:] - if len(s) == 0 { // can be at end - break - } - } - } - - // Must have used entire string. - if len(s) != 0 { - return nil - } - - // If didn't parse enough, expand ellipsis. - if i < IPv6len { - if ellipsis < 0 { - return nil - } - n := IPv6len - i - for j := i - 1; j >= ellipsis; j-- { - ip[j+n] = ip[j] - } - for j := ellipsis + n - 1; j >= ellipsis; j-- { - ip[j] = 0 - } - } else if ellipsis >= 0 { - // Ellipsis must represent at least one 0 group. - return nil - } - return ip -} - // ParseIP parses s as an IP address, returning the result. // The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6 // ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form. // If s is not a valid textual representation of an IP address, // ParseIP returns nil. func ParseIP(s string) IP { - for i := 0; i < len(s); i++ { - switch s[i] { - case '.': - return parseIPv4(s) - case ':': - return parseIPv6(s) - } + if addr, valid := parseIP(s); valid { + return IP(addr[:]) } return nil } -// parseIPZone parses s as an IP address, return it and its associated zone -// identifier (IPv6 only). -func parseIPZone(s string) (IP, string) { - for i := 0; i < len(s); i++ { - switch s[i] { - case '.': - return parseIPv4(s), "" - case ':': - return parseIPv6Zone(s) - } +func parseIP(s string) ([16]byte, bool) { + ip, err := netip.ParseAddr(s) + if err != nil || ip.Zone() != "" { + return [16]byte{}, false } - return nil, "" + return ip.As16(), true } // ParseCIDR parses s as a CIDR notation IP address and prefix length, @@ -744,18 +520,19 @@ func ParseCIDR(s string) (IP, *IPNet, error) { return nil, nil, &ParseError{Type: "CIDR address", Text: s} } addr, mask := s[:i], s[i+1:] - iplen := IPv4len - ip := parseIPv4(addr) - if ip == nil { - iplen = IPv6len - ip = parseIPv6(addr) + + ipAddr, err := netip.ParseAddr(addr) + if err != nil || ipAddr.Zone() != "" { + return nil, nil, &ParseError{Type: "CIDR address", Text: s} } + n, i, ok := dtoi(mask) - if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen { + if !ok || i != len(mask) || n < 0 || n > ipAddr.BitLen() { return nil, nil, &ParseError{Type: "CIDR address", Text: s} } - m := CIDRMask(n, 8*iplen) - return ip, &IPNet{IP: ip.Mask(m), Mask: m}, nil + m := CIDRMask(n, ipAddr.BitLen()) + addr16 := ipAddr.As16() + return IP(addr16[:]), &IPNet{IP: IP(addr16[:]).Mask(m), Mask: m}, nil } func copyIP(x IP) IP { diff --git a/src/net/ip_test.go b/src/net/ip_test.go index 8f1590cfd5..68ada4939a 100644 --- a/src/net/ip_test.go +++ b/src/net/ip_test.go @@ -116,6 +116,22 @@ func BenchmarkParseIP(b *testing.B) { } } +func BenchmarkParseIPValidIPv4(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + + for i := 0; i < b.N; i++ { + ParseIP("192.0.2.1") + } +} + +func BenchmarkParseIPValidIPv6(b *testing.B) { + testHookUninstaller.Do(uninstallTestHooks) + + for i := 0; i < b.N; i++ { + ParseIP("2001:DB8::1") + } +} + // Issue 6339 func TestMarshalEmptyIP(t *testing.T) { for _, in := range [][]byte{nil, []byte("")} { diff --git a/src/net/lookup.go b/src/net/lookup.go index 0fd5d2b2c7..a7133b53ac 100644 --- a/src/net/lookup.go +++ b/src/net/lookup.go @@ -181,11 +181,10 @@ func LookupHost(host string) (addrs []string, err error) { // It returns a slice of that host's addresses. func (r *Resolver) LookupHost(ctx context.Context, 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, IsNotFound: true} } - if ip, _ := parseIPZone(host); ip != nil { + if _, err := netip.ParseAddr(host); err == nil { return []string{host}, nil } return r.lookupHost(ctx, host) @@ -294,12 +293,11 @@ func withUnexpiredValuesPreserved(lookupCtx context.Context) context.Context { // It returns a slice of that host's IPv4 and IPv6 addresses. func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IPAddr, error) { // Make sure that no matter what we do later, host=="" is rejected. - // parseIPZone, for example, does accept empty strings. if host == "" { return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} } - if ip, zone := parseIPZone(host); ip != nil { - return []IPAddr{{IP: ip, Zone: zone}}, nil + if ip, err := netip.ParseAddr(host); err == nil { + return []IPAddr{{IP: IP(ip.AsSlice()).To16(), Zone: ip.Zone()}}, nil } trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace) if trace != nil && trace.DNSStart != nil { -- 2.50.0