]> Cypherpunks repositories - gostls13.git/commitdiff
net: use net/netip for address parsing
authorMateusz Poliwczak <mpoliwczak34@gmail.com>
Tue, 31 Jan 2023 09:00:22 +0000 (09:00 +0000)
committerGopher Robot <gobot@golang.org>
Tue, 31 Jan 2023 22:29:57 +0000 (22:29 +0000)
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 <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>

src/net/cgo_unix.go
src/net/dnsconfig_unix.go
src/net/hosts.go
src/net/ip.go
src/net/ip_test.go
src/net/lookup.go

index 8e43bad8b6fed2202c00c22003dada29d7d58e8b..6e43e8271a9cce50ba4c36065ee1dec620ed0bc6 100644 (file)
@@ -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
        }
index 8f6ae34c1b31eea018e2a3e2d5f45bc285179826..69b300410ab0e18e74b2b7e4a2ee682277c54537 100644 (file)
@@ -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"))
                                }
                        }
index dbf8fea136d7731c7daaff0fa5c7130ce346bdee..8b954188bf12d2e1d383114552307998bf5908dc 100644 (file)
@@ -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 {
index d9f3da702127342f0029ae4b06867443e8f99237..d51ba10eec0274d45f790b549b065de9a0671968 100644 (file)
@@ -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:
 //   - "<nil>", 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 "<nil>"
        }
 
-       // 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 {
index 8f1590cfd5cd1952fe75bfa986c2508fcb9812c3..68ada4939ae16d73b4640f1ad9a261ad1126f4ce 100644 (file)
@@ -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("")} {
index 0fd5d2b2c728d3e3dddc5bbcad36fad76584f595..a7133b53ac136de0f7d669edf86d14658071d07e 100644 (file)
@@ -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 {