1
0
mirror of https://github.com/golang/go synced 2024-07-05 09:50:19 +00:00

net/netip: add new IP address package

Co-authored-by: Alex Willmer <alex@moreati.org.uk> (GitHub @moreati)
Co-authored-by: Alexander Yastrebov <yastrebov.alex@gmail.com>
Co-authored-by: David Anderson <dave@natulte.net> (Tailscale CLA)
Co-authored-by: David Crawshaw <crawshaw@tailscale.com> (Tailscale CLA)
Co-authored-by: Dmytro Shynkevych <dmytro@tailscale.com> (Tailscale CLA)
Co-authored-by: Elias Naur <mail@eliasnaur.com>
Co-authored-by: Joe Tsai <joetsai@digital-static.net> (Tailscale CLA)
Co-authored-by: Jonathan Yu <jawnsy@cpan.org> (GitHub @jawnsy)
Co-authored-by: Josh Bleecher Snyder <josharian@gmail.com> (Tailscale CLA)
Co-authored-by: Maisem Ali <maisem@tailscale.com> (Tailscale CLA)
Co-authored-by: Manuel Mendez (Go AUTHORS mmendez534@...)
Co-authored-by: Matt Layher <mdlayher@gmail.com>
Co-authored-by: Noah Treuhaft <noah.treuhaft@gmail.com> (GitHub @nwt)
Co-authored-by: Stefan Majer <stefan.majer@gmail.com>
Co-authored-by: Terin Stock <terinjokes@gmail.com> (Cloudflare CLA)
Co-authored-by: Tobias Klauser <tklauser@distanz.ch>

Fixes #46518

Change-Id: I0041f9e1115d61fa6e95fcf32b01d9faee708712
Reviewed-on: https://go-review.googlesource.com/c/go/+/339309
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Trust: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Brad Fitzpatrick 2021-08-02 14:55:51 -07:00
parent 81fea0b4fd
commit a59e33224e
26 changed files with 4694 additions and 68 deletions

View File

@ -21,7 +21,7 @@ func Equal(a, b []byte) bool {
}
// Compare returns an integer comparing two byte slices lexicographically.
// The result will be 0 if a==b, -1 if a < b, and +1 if a > b.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
// A nil argument is equivalent to an empty slice.
func Compare(a, b []byte) int {
return bytealg.Compare(a, b)

View File

@ -10,11 +10,11 @@ import (
"bytes"
macOS "crypto/x509/internal/macos"
"fmt"
"internal/godebug"
"os"
"strings"
)
var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
var debugDarwinRoots = godebug.Get("x509roots") == "1"
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil

View File

@ -173,7 +173,7 @@ var depsRules = `
io/fs
< embed;
unicode, fmt !< os, os/signal;
unicode, fmt !< net, os, os/signal;
os/signal, STR
< path/filepath
@ -187,6 +187,8 @@ var depsRules = `
OS
< golang.org/x/sys/cpu;
os < internal/godebug;
# FMT is OS (which includes string routines) plus reflect and fmt.
# It does not include package log, which should be avoided in core packages.
strconv, unicode
@ -352,6 +354,13 @@ var depsRules = `
golang.org/x/net/lif,
golang.org/x/net/route;
os, runtime, strconv, sync, unsafe,
internal/godebug
< internal/intern;
internal/bytealg, internal/intern, internal/itoa, math/bits, sort, strconv
< net/netip;
# net is unavoidable when doing any networking,
# so large dependencies must be kept out.
# This is a long-looking list but most of these
@ -360,10 +369,12 @@ var depsRules = `
golang.org/x/net/dns/dnsmessage,
golang.org/x/net/lif,
golang.org/x/net/route,
internal/godebug,
internal/nettrace,
internal/poll,
internal/singleflight,
internal/race,
net/netip,
os
< net;
@ -515,7 +526,8 @@ var depsRules = `
FMT, DEBUG, flag, runtime/trace, internal/sysinfo, math/rand
< testing;
FMT, crypto/sha256, encoding/json, go/ast, go/parser, go/token, math/rand, encoding/hex, crypto/sha256
FMT, crypto/sha256, encoding/json, go/ast, go/parser, go/token,
internal/godebug, math/rand, encoding/hex, crypto/sha256
< internal/fuzz;
internal/fuzz, internal/testlog, runtime/pprof, regexp

View File

@ -12,6 +12,7 @@ import (
"crypto/sha256"
"errors"
"fmt"
"internal/godebug"
"io"
"io/ioutil"
"math/bits"
@ -1063,13 +1064,7 @@ var (
func shouldPrintDebugInfo() bool {
debugInfoOnce.Do(func() {
debug := strings.Split(os.Getenv("GODEBUG"), ",")
for _, f := range debug {
if f == "fuzzdebug=1" {
debugInfo = true
break
}
}
debugInfo = godebug.Get("fuzzdebug") == "1"
})
return debugInfo
}

View File

@ -0,0 +1,34 @@
// Copyright 2021 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 godebug parses the GODEBUG environment variable.
package godebug
import "os"
// Get returns the value for the provided GODEBUG key.
func Get(key string) string {
return get(os.Getenv("GODEBUG"), key)
}
// get returns the value part of key=value in s (a GODEBUG value).
func get(s, key string) string {
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 ""
}

View File

@ -0,0 +1,34 @@
// Copyright 2021 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 godebug
import "testing"
func TestGet(t *testing.T) {
tests := []struct {
godebug string
key string
want string
}{
{"", "", ""},
{"", "foo", ""},
{"foo=bar", "foo", "bar"},
{"foo=bar,after=x", "foo", "bar"},
{"before=x,foo=bar,after=x", "foo", "bar"},
{"before=x,foo=bar", "foo", "bar"},
{",,,foo=bar,,,", "foo", "bar"},
{"foodecoy=wrong,foo=bar", "foo", "bar"},
{"foo=", "foo", ""},
{"foo", "foo", ""},
{",foo", "foo", ""},
{"foo=bar,baz", "loooooooong", ""},
}
for _, tt := range tests {
got := get(tt.godebug, tt.key)
if got != tt.want {
t.Errorf("get(%q, %q) = %q; want %q", tt.godebug, tt.key, got, tt.want)
}
}
}

View File

@ -0,0 +1,178 @@
// Copyright 2020 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 intern lets you make smaller comparable values by boxing
// a larger comparable value (such as a 16 byte string header) down
// into a globally unique 8 byte pointer.
//
// The globally unique pointers are garbage collected with weak
// references and finalizers. This package hides that.
package intern
import (
"internal/godebug"
"runtime"
"sync"
"unsafe"
)
// A Value pointer is the handle to an underlying comparable value.
// See func Get for how Value pointers may be used.
type Value struct {
_ [0]func() // prevent people from accidentally using value type as comparable
cmpVal interface{}
// resurrected is guarded by mu (for all instances of Value).
// It is set true whenever v is synthesized from a uintptr.
resurrected bool
}
// Get returns the comparable value passed to the Get func
// that returned v.
func (v *Value) Get() interface{} { return v.cmpVal }
// key is a key in our global value map.
// It contains type-specialized fields to avoid allocations
// when converting common types to empty interfaces.
type key struct {
s string
cmpVal interface{}
// isString reports whether key contains a string.
// Without it, the zero value of key is ambiguous.
isString bool
}
// keyFor returns a key to use with cmpVal.
func keyFor(cmpVal interface{}) key {
if s, ok := cmpVal.(string); ok {
return key{s: s, isString: true}
}
return key{cmpVal: cmpVal}
}
// Value returns a *Value built from k.
func (k key) Value() *Value {
if k.isString {
return &Value{cmpVal: k.s}
}
return &Value{cmpVal: k.cmpVal}
}
var (
// mu guards valMap, a weakref map of *Value by underlying value.
// It also guards the resurrected field of all *Values.
mu sync.Mutex
valMap = map[key]uintptr{} // to uintptr(*Value)
valSafe = safeMap() // non-nil in safe+leaky mode
)
// safeMap returns a non-nil map if we're in safe-but-leaky mode,
// as controlled by GODEBUG=intern=leaky
func safeMap() map[key]*Value {
if godebug.Get("intern") == "leaky" {
return map[key]*Value{}
}
return nil
}
// Get returns a pointer representing the comparable value cmpVal.
//
// The returned pointer will be the same for Get(v) and Get(v2)
// if and only if v == v2, and can be used as a map key.
func Get(cmpVal interface{}) *Value {
return get(keyFor(cmpVal))
}
// GetByString is identical to Get, except that it is specialized for strings.
// This avoids an allocation from putting a string into an interface{}
// to pass as an argument to Get.
func GetByString(s string) *Value {
return get(key{s: s, isString: true})
}
// We play unsafe games that violate Go's rules (and assume a non-moving
// collector). So we quiet Go here.
// See the comment below Get for more implementation details.
//go:nocheckptr
func get(k key) *Value {
mu.Lock()
defer mu.Unlock()
var v *Value
if valSafe != nil {
v = valSafe[k]
} else if addr, ok := valMap[k]; ok {
v = (*Value)(unsafe.Pointer(addr))
v.resurrected = true
}
if v != nil {
return v
}
v = k.Value()
if valSafe != nil {
valSafe[k] = v
} else {
// SetFinalizer before uintptr conversion (theoretical concern;
// see https://github.com/go4org/intern/issues/13)
runtime.SetFinalizer(v, finalize)
valMap[k] = uintptr(unsafe.Pointer(v))
}
return v
}
func finalize(v *Value) {
mu.Lock()
defer mu.Unlock()
if v.resurrected {
// We lost the race. Somebody resurrected it while we
// were about to finalize it. Try again next round.
v.resurrected = false
runtime.SetFinalizer(v, finalize)
return
}
delete(valMap, keyFor(v.cmpVal))
}
// Interning is simple if you don't require that unused values be
// garbage collectable. But we do require that; we don't want to be
// DOS vector. We do this by using a uintptr to hide the pointer from
// the garbage collector, and using a finalizer to eliminate the
// pointer when no other code is using it.
//
// The obvious implementation of this is to use a
// map[interface{}]uintptr-of-*interface{}, and set up a finalizer to
// delete from the map. Unfortunately, this is racy. Because pointers
// are being created in violation of Go's unsafety rules, it's
// possible to create a pointer to a value concurrently with the GC
// concluding that the value can be collected. There are other races
// that break the equality invariant as well, but the use-after-free
// will cause a runtime crash.
//
// To make this work, the finalizer needs to know that no references
// have been unsafely created since the finalizer was set up. To do
// this, values carry a "resurrected" sentinel, which gets set
// whenever a pointer is unsafely created. If the finalizer encounters
// the sentinel, it clears the sentinel and delays collection for one
// additional GC cycle, by re-installing itself as finalizer. This
// ensures that the unsafely created pointer is visible to the GC, and
// will correctly prevent collection.
//
// This technique does mean that interned values that get reused take
// at least 3 GC cycles to fully collect (1 to clear the sentinel, 1
// to clean up the unsafe map, 1 to be actually deleted).
//
// @ianlancetaylor commented in
// https://github.com/golang/go/issues/41303#issuecomment-717401656
// that it is possible to implement weak references in terms of
// finalizers without unsafe. Unfortunately, the approach he outlined
// does not work here, for two reasons. First, there is no way to
// construct a strong pointer out of a weak pointer; our map stores
// weak pointers, but we must return strong pointers to callers.
// Second, and more fundamentally, we must return not just _a_ strong
// pointer to callers, but _the same_ strong pointer to callers. In
// order to return _the same_ strong pointer to callers, we must track
// it, which is exactly what we cannot do with strong pointers.
//
// See https://github.com/inetaf/netaddr/issues/53 for more
// discussion, and https://github.com/go4org/intern/issues/2 for an
// illustration of the subtleties at play.

View File

@ -0,0 +1,199 @@
// Copyright 2020 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 intern
import (
"fmt"
"runtime"
"testing"
)
func TestBasics(t *testing.T) {
clearMap()
foo := Get("foo")
bar := Get("bar")
empty := Get("")
nilEface := Get(nil)
i := Get(0x7777777)
foo2 := Get("foo")
bar2 := Get("bar")
empty2 := Get("")
nilEface2 := Get(nil)
i2 := Get(0x7777777)
foo3 := GetByString("foo")
empty3 := GetByString("")
if foo.Get() != foo2.Get() {
t.Error("foo/foo2 values differ")
}
if foo.Get() != foo3.Get() {
t.Error("foo/foo3 values differ")
}
if foo.Get() != "foo" {
t.Error("foo.Get not foo")
}
if foo != foo2 {
t.Error("foo/foo2 pointers differ")
}
if foo != foo3 {
t.Error("foo/foo3 pointers differ")
}
if bar.Get() != bar2.Get() {
t.Error("bar values differ")
}
if bar.Get() != "bar" {
t.Error("bar.Get not bar")
}
if bar != bar2 {
t.Error("bar pointers differ")
}
if i.Get() != i.Get() {
t.Error("i values differ")
}
if i.Get() != 0x7777777 {
t.Error("i.Get not 0x7777777")
}
if i != i2 {
t.Error("i pointers differ")
}
if empty.Get() != empty2.Get() {
t.Error("empty/empty2 values differ")
}
if empty.Get() != empty.Get() {
t.Error("empty/empty3 values differ")
}
if empty.Get() != "" {
t.Error("empty.Get not empty string")
}
if empty != empty2 {
t.Error("empty/empty2 pointers differ")
}
if empty != empty3 {
t.Error("empty/empty3 pointers differ")
}
if nilEface.Get() != nilEface2.Get() {
t.Error("nilEface values differ")
}
if nilEface.Get() != nil {
t.Error("nilEface.Get not nil")
}
if nilEface != nilEface2 {
t.Error("nilEface pointers differ")
}
if n := mapLen(); n != 5 {
t.Errorf("map len = %d; want 4", n)
}
wantEmpty(t)
}
func wantEmpty(t testing.TB) {
t.Helper()
const gcTries = 5000
for try := 0; try < gcTries; try++ {
runtime.GC()
n := mapLen()
if n == 0 {
break
}
if try == gcTries-1 {
t.Errorf("map len = %d after (%d GC tries); want 0, contents: %v", n, gcTries, mapKeys())
}
}
}
func TestStress(t *testing.T) {
iters := 10000
if testing.Short() {
iters = 1000
}
var sink []byte
for i := 0; i < iters; i++ {
_ = Get("foo")
sink = make([]byte, 1<<20)
}
_ = sink
}
func BenchmarkStress(b *testing.B) {
done := make(chan struct{})
defer close(done)
go func() {
for {
select {
case <-done:
return
default:
}
runtime.GC()
}
}()
clearMap()
v1 := Get("foo")
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v2 := Get("foo")
if v1 != v2 {
b.Fatal("wrong value")
}
// And also a key we don't retain:
_ = Get("bar")
}
})
runtime.GC()
wantEmpty(b)
}
func mapLen() int {
mu.Lock()
defer mu.Unlock()
return len(valMap)
}
func mapKeys() (keys []string) {
mu.Lock()
defer mu.Unlock()
for k := range valMap {
keys = append(keys, fmt.Sprint(k))
}
return keys
}
func clearMap() {
mu.Lock()
defer mu.Unlock()
for k := range valMap {
delete(valMap, k)
}
}
var (
globalString = "not a constant"
sink string
)
func TestGetByStringAllocs(t *testing.T) {
allocs := int(testing.AllocsPerRun(100, func() {
GetByString(globalString)
}))
if allocs != 0 {
t.Errorf("GetString allocated %d objects, want 0", allocs)
}
}
func BenchmarkGetByString(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
v := GetByString(globalString)
sink = v.Get().(string)
}
}

View File

@ -8,6 +8,7 @@ package net
import (
"internal/bytealg"
"internal/godebug"
"os"
"runtime"
"sync"
@ -286,7 +287,7 @@ func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrde
// cgo+2 // same, but debug level 2
// etc.
func goDebugNetDNS() (dnsMode string, debugLevel int) {
goDebug := goDebugString("netdns")
goDebug := godebug.Get("netdns")
parsePart := func(s string) {
if s == "" {
return

View File

@ -13,6 +13,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"internal/godebug"
"io"
"log"
"math/rand"
@ -20,7 +21,6 @@ import (
"net/textproto"
"net/url"
urlpkg "net/url"
"os"
"path"
"runtime"
"sort"
@ -3296,7 +3296,7 @@ func (srv *Server) onceSetNextProtoDefaults_Serve() {
// configured otherwise. (by setting srv.TLSNextProto non-nil)
// It must only be called via srv.nextProtoOnce (use srv.setupHTTP2_*).
func (srv *Server) onceSetNextProtoDefaults() {
if omitBundledHTTP2 || strings.Contains(os.Getenv("GODEBUG"), "http2server=0") {
if omitBundledHTTP2 || godebug.Get("http2server") == "0" {
return
}
// Enable HTTP/2 by default if the user hasn't otherwise

View File

@ -17,6 +17,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"internal/godebug"
"io"
"log"
"net"
@ -24,7 +25,6 @@ import (
"net/http/internal/ascii"
"net/textproto"
"net/url"
"os"
"reflect"
"strings"
"sync"
@ -360,7 +360,7 @@ func (t *Transport) hasCustomTLSDialer() bool {
// It must be called via t.nextProtoOnce.Do.
func (t *Transport) onceSetNextProtoDefaults() {
t.tlsNextProtoWasNil = (t.TLSNextProto == nil)
if strings.Contains(os.Getenv("GODEBUG"), "http2client=0") {
if godebug.Get("http2client") == "0" {
return
}

View File

@ -8,6 +8,7 @@ import (
"context"
"internal/nettrace"
"internal/singleflight"
"net/netip"
"sync"
)
@ -232,6 +233,28 @@ func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]IP, er
return ips, nil
}
// LookupNetIP looks up host using the local resolver.
// It returns a slice of that host's IP addresses of the type specified by
// network.
// The network must be one of "ip", "ip4" or "ip6".
func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
ips, err := r.LookupIP(ctx, network, host)
if err != nil {
return nil, err
}
ret := make([]netip.Addr, 0, len(ips))
for _, ip := range ips {
if a, ok := netip.AddrFromSlice(ip); ok {
ret = append(ret, a)
}
}
return ret, nil
}
// onlyValuesCtx is a context that uses an underlying context
// for value lookup if the underlying context hasn't yet expired.
type onlyValuesCtx struct {

View File

@ -0,0 +1,30 @@
// Copyright 2021 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 netip
import "internal/intern"
var (
Z0 = z0
Z4 = z4
Z6noz = z6noz
)
type Uint128 = uint128
func Mk128(hi, lo uint64) Uint128 {
return uint128{hi, lo}
}
func MkAddr(u Uint128, z *intern.Value) Addr {
return Addr{u, z}
}
func IPv4(a, b, c, d uint8) Addr { return AddrFrom4([4]byte{a, b, c, d}) }
var TestAppendToMarshal = testAppendToMarshal
func (a Addr) IsZero() bool { return a.isZero() }
func (p Prefix) IsZero() bool { return p.isZero() }

View File

@ -0,0 +1,110 @@
// Copyright 2020 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 netip
import (
"internal/testenv"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
)
func TestInlining(t *testing.T) {
testenv.MustHaveGoBuild(t)
t.Parallel()
var exe string
if runtime.GOOS == "windows" {
exe = ".exe"
}
out, err := exec.Command(
filepath.Join(runtime.GOROOT(), "bin", "go"+exe),
"build",
"--gcflags=-m",
"net/netip").CombinedOutput()
if err != nil {
t.Fatalf("go build: %v, %s", err, out)
}
got := map[string]bool{}
regexp.MustCompile(` can inline (\S+)`).ReplaceAllFunc(out, func(match []byte) []byte {
got[strings.TrimPrefix(string(match), " can inline ")] = true
return nil
})
wantInlinable := []string{
"(*uint128).halves",
"Addr.BitLen",
"Addr.hasZone",
"Addr.Is4",
"Addr.Is4In6",
"Addr.Is6",
"Addr.IsLoopback",
"Addr.IsMulticast",
"Addr.IsInterfaceLocalMulticast",
"Addr.IsValid",
"Addr.IsUnspecified",
"Addr.Less",
"Addr.lessOrEq",
"Addr.Unmap",
"Addr.Zone",
"Addr.v4",
"Addr.v6",
"Addr.v6u16",
"Addr.withoutZone",
"AddrPortFrom",
"AddrPort.Addr",
"AddrPort.Port",
"AddrPort.IsValid",
"Prefix.IsSingleIP",
"Prefix.Masked",
"Prefix.IsValid",
"PrefixFrom",
"Prefix.Addr",
"Prefix.Bits",
"AddrFrom4",
"IPv6LinkLocalAllNodes",
"IPv6Unspecified",
"MustParseAddr",
"MustParseAddrPort",
"MustParsePrefix",
"appendDecimal",
"appendHex",
"uint128.addOne",
"uint128.and",
"uint128.bitsClearedFrom",
"uint128.bitsSetFrom",
"uint128.isZero",
"uint128.not",
"uint128.or",
"uint128.subOne",
"uint128.xor",
}
switch runtime.GOARCH {
case "amd64", "arm64":
// These don't inline on 32-bit.
wantInlinable = append(wantInlinable,
"u64CommonPrefixLen",
"uint128.commonPrefixLen",
"Addr.Next",
"Addr.Prev",
)
}
for _, want := range wantInlinable {
if !got[want] {
t.Errorf("%q is no longer inlinable", want)
continue
}
delete(got, want)
}
for sym := range got {
if strings.Contains(sym, ".func") {
continue
}
t.Logf("not in expected set, but also inlinable: %q", sym)
}
}

View File

@ -0,0 +1,43 @@
// Copyright 2021 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.
// Stuff that exists in std, but we can't use due to being a dependency
// of net, for go/build deps_test policy reasons.
package netip
func stringsLastIndexByte(s string, b byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == b {
return i
}
}
return -1
}
func beUint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}
func bePutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}
func bePutUint32(b []byte, v uint32) {
_ = b[3] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
}

1414
src/net/netip/netip.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,359 @@
// Copyright 2020 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 netip
import (
"bytes"
"encoding"
"encoding/json"
"strings"
"testing"
)
var (
mustPrefix = MustParsePrefix
mustIP = MustParseAddr
)
func TestPrefixValid(t *testing.T) {
v4 := MustParseAddr("1.2.3.4")
v6 := MustParseAddr("::1")
tests := []struct {
ipp Prefix
want bool
}{
{Prefix{v4, -2}, false},
{Prefix{v4, -1}, false},
{Prefix{v4, 0}, true},
{Prefix{v4, 32}, true},
{Prefix{v4, 33}, false},
{Prefix{v6, -2}, false},
{Prefix{v6, -1}, false},
{Prefix{v6, 0}, true},
{Prefix{v6, 32}, true},
{Prefix{v6, 128}, true},
{Prefix{v6, 129}, false},
{Prefix{Addr{}, -2}, false},
{Prefix{Addr{}, -1}, false},
{Prefix{Addr{}, 0}, false},
{Prefix{Addr{}, 32}, false},
{Prefix{Addr{}, 128}, false},
}
for _, tt := range tests {
got := tt.ipp.IsValid()
if got != tt.want {
t.Errorf("(%v).IsValid() = %v want %v", tt.ipp, got, tt.want)
}
}
}
var nextPrevTests = []struct {
ip Addr
next Addr
prev Addr
}{
{mustIP("10.0.0.1"), mustIP("10.0.0.2"), mustIP("10.0.0.0")},
{mustIP("10.0.0.255"), mustIP("10.0.1.0"), mustIP("10.0.0.254")},
{mustIP("127.0.0.1"), mustIP("127.0.0.2"), mustIP("127.0.0.0")},
{mustIP("254.255.255.255"), mustIP("255.0.0.0"), mustIP("254.255.255.254")},
{mustIP("255.255.255.255"), Addr{}, mustIP("255.255.255.254")},
{mustIP("0.0.0.0"), mustIP("0.0.0.1"), Addr{}},
{mustIP("::"), mustIP("::1"), Addr{}},
{mustIP("::%x"), mustIP("::1%x"), Addr{}},
{mustIP("::1"), mustIP("::2"), mustIP("::")},
{mustIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), Addr{}, mustIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe")},
}
func TestIPNextPrev(t *testing.T) {
doNextPrev(t)
for _, ip := range []Addr{
mustIP("0.0.0.0"),
mustIP("::"),
} {
got := ip.Prev()
if !got.isZero() {
t.Errorf("IP(%v).Prev = %v; want zero", ip, got)
}
}
var allFF [16]byte
for i := range allFF {
allFF[i] = 0xff
}
for _, ip := range []Addr{
mustIP("255.255.255.255"),
AddrFrom16(allFF),
} {
got := ip.Next()
if !got.isZero() {
t.Errorf("IP(%v).Next = %v; want zero", ip, got)
}
}
}
func BenchmarkIPNextPrev(b *testing.B) {
for i := 0; i < b.N; i++ {
doNextPrev(b)
}
}
func doNextPrev(t testing.TB) {
for _, tt := range nextPrevTests {
gnext, gprev := tt.ip.Next(), tt.ip.Prev()
if gnext != tt.next {
t.Errorf("IP(%v).Next = %v; want %v", tt.ip, gnext, tt.next)
}
if gprev != tt.prev {
t.Errorf("IP(%v).Prev = %v; want %v", tt.ip, gprev, tt.prev)
}
if !tt.ip.Next().isZero() && tt.ip.Next().Prev() != tt.ip {
t.Errorf("IP(%v).Next.Prev = %v; want %v", tt.ip, tt.ip.Next().Prev(), tt.ip)
}
if !tt.ip.Prev().isZero() && tt.ip.Prev().Next() != tt.ip {
t.Errorf("IP(%v).Prev.Next = %v; want %v", tt.ip, tt.ip.Prev().Next(), tt.ip)
}
}
}
func TestIPBitLen(t *testing.T) {
tests := []struct {
ip Addr
want int
}{
{Addr{}, 0},
{mustIP("0.0.0.0"), 32},
{mustIP("10.0.0.1"), 32},
{mustIP("::"), 128},
{mustIP("fed0::1"), 128},
{mustIP("::ffff:10.0.0.1"), 128},
}
for _, tt := range tests {
got := tt.ip.BitLen()
if got != tt.want {
t.Errorf("BitLen(%v) = %d; want %d", tt.ip, got, tt.want)
}
}
}
func TestPrefixContains(t *testing.T) {
tests := []struct {
ipp Prefix
ip Addr
want bool
}{
{mustPrefix("9.8.7.6/0"), mustIP("9.8.7.6"), true},
{mustPrefix("9.8.7.6/16"), mustIP("9.8.7.6"), true},
{mustPrefix("9.8.7.6/16"), mustIP("9.8.6.4"), true},
{mustPrefix("9.8.7.6/16"), mustIP("9.9.7.6"), false},
{mustPrefix("9.8.7.6/32"), mustIP("9.8.7.6"), true},
{mustPrefix("9.8.7.6/32"), mustIP("9.8.7.7"), false},
{mustPrefix("9.8.7.6/32"), mustIP("9.8.7.7"), false},
{mustPrefix("::1/0"), mustIP("::1"), true},
{mustPrefix("::1/0"), mustIP("::2"), true},
{mustPrefix("::1/127"), mustIP("::1"), true},
{mustPrefix("::1/127"), mustIP("::2"), false},
{mustPrefix("::1/128"), mustIP("::1"), true},
{mustPrefix("::1/127"), mustIP("::2"), false},
// zones support
{mustPrefix("::1%a/128"), mustIP("::1"), true}, // prefix zones are stripped...
{mustPrefix("::1%a/128"), mustIP("::1%a"), false}, // but ip zones are not
// invalid IP
{mustPrefix("::1/0"), Addr{}, false},
{mustPrefix("1.2.3.4/0"), Addr{}, false},
// invalid Prefix
{Prefix{mustIP("::1"), 129}, mustIP("::1"), false},
{Prefix{mustIP("1.2.3.4"), 33}, mustIP("1.2.3.4"), false},
{Prefix{Addr{}, 0}, mustIP("1.2.3.4"), false},
{Prefix{Addr{}, 32}, mustIP("1.2.3.4"), false},
{Prefix{Addr{}, 128}, mustIP("::1"), false},
// wrong IP family
{mustPrefix("::1/0"), mustIP("1.2.3.4"), false},
{mustPrefix("1.2.3.4/0"), mustIP("::1"), false},
}
for _, tt := range tests {
got := tt.ipp.Contains(tt.ip)
if got != tt.want {
t.Errorf("(%v).Contains(%v) = %v want %v", tt.ipp, tt.ip, got, tt.want)
}
}
}
func TestParseIPError(t *testing.T) {
tests := []struct {
ip string
errstr string
}{
{
ip: "localhost",
},
{
ip: "500.0.0.1",
errstr: "field has value >255",
},
{
ip: "::gggg%eth0",
errstr: "must have at least one digit",
},
{
ip: "fe80::1cc0:3e8c:119f:c2e1%",
errstr: "zone must be a non-empty string",
},
{
ip: "%eth0",
errstr: "missing IPv6 address",
},
}
for _, test := range tests {
t.Run(test.ip, func(t *testing.T) {
_, err := ParseAddr(test.ip)
if err == nil {
t.Fatal("no error")
}
if _, ok := err.(parseAddrError); !ok {
t.Errorf("error type is %T, want parseIPError", err)
}
if test.errstr == "" {
test.errstr = "unable to parse IP"
}
if got := err.Error(); !strings.Contains(got, test.errstr) {
t.Errorf("error is missing substring %q: %s", test.errstr, got)
}
})
}
}
func TestParseAddrPort(t *testing.T) {
tests := []struct {
in string
want AddrPort
wantErr bool
}{
{in: "1.2.3.4:1234", want: AddrPort{mustIP("1.2.3.4"), 1234}},
{in: "1.1.1.1:123456", wantErr: true},
{in: "1.1.1.1:-123", wantErr: true},
{in: "[::1]:1234", want: AddrPort{mustIP("::1"), 1234}},
{in: "[1.2.3.4]:1234", wantErr: true},
{in: "fe80::1:1234", wantErr: true},
{in: ":0", wantErr: true}, // if we need to parse this form, there should be a separate function that explicitly allows it
}
for _, test := range tests {
t.Run(test.in, func(t *testing.T) {
got, err := ParseAddrPort(test.in)
if err != nil {
if test.wantErr {
return
}
t.Fatal(err)
}
if got != test.want {
t.Errorf("got %v; want %v", got, test.want)
}
if got.String() != test.in {
t.Errorf("String = %q; want %q", got.String(), test.in)
}
})
t.Run(test.in+"/AppendTo", func(t *testing.T) {
got, err := ParseAddrPort(test.in)
if err == nil {
testAppendToMarshal(t, got)
}
})
// TextMarshal and TextUnmarshal mostly behave like
// ParseAddrPort and String. Divergent behavior are handled in
// TestAddrPortMarshalUnmarshal.
t.Run(test.in+"/Marshal", func(t *testing.T) {
var got AddrPort
jsin := `"` + test.in + `"`
err := json.Unmarshal([]byte(jsin), &got)
if err != nil {
if test.wantErr {
return
}
t.Fatal(err)
}
if got != test.want {
t.Errorf("got %v; want %v", got, test.want)
}
gotb, err := json.Marshal(got)
if err != nil {
t.Fatal(err)
}
if string(gotb) != jsin {
t.Errorf("Marshal = %q; want %q", string(gotb), jsin)
}
})
}
}
func TestAddrPortMarshalUnmarshal(t *testing.T) {
tests := []struct {
in string
want AddrPort
}{
{"", AddrPort{}},
}
for _, test := range tests {
t.Run(test.in, func(t *testing.T) {
orig := `"` + test.in + `"`
var ipp AddrPort
if err := json.Unmarshal([]byte(orig), &ipp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
ippb, err := json.Marshal(ipp)
if err != nil {
t.Fatalf("failed to marshal: %v", err)
}
back := string(ippb)
if orig != back {
t.Errorf("Marshal = %q; want %q", back, orig)
}
testAppendToMarshal(t, ipp)
})
}
}
type appendMarshaler interface {
encoding.TextMarshaler
AppendTo([]byte) []byte
}
// testAppendToMarshal tests that x's AppendTo and MarshalText methods yield the same results.
// x's MarshalText method must not return an error.
func testAppendToMarshal(t *testing.T, x appendMarshaler) {
t.Helper()
m, err := x.MarshalText()
if err != nil {
t.Fatalf("(%v).MarshalText: %v", x, err)
}
a := make([]byte, 0, len(m))
a = x.AppendTo(a)
if !bytes.Equal(m, a) {
t.Errorf("(%v).MarshalText = %q, (%v).AppendTo = %q", x, m, x, a)
}
}
func TestIPv6Accessor(t *testing.T) {
var a [16]byte
for i := range a {
a[i] = uint8(i) + 1
}
ip := AddrFrom16(a)
for i := range a {
if got, want := ip.v6(uint8(i)), uint8(i)+1; got != want {
t.Errorf("v6(%v) = %v; want %v", i, got, want)
}
}
}

1798
src/net/netip/netip_test.go Normal file

File diff suppressed because it is too large Load Diff

190
src/net/netip/slow_test.go Normal file
View File

@ -0,0 +1,190 @@
// Copyright 2020 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 netip_test
import (
"fmt"
. "net/netip"
"strconv"
"strings"
)
// zeros is a slice of eight stringified zeros. It's used in
// parseIPSlow to construct slices of specific amounts of zero fields,
// from 1 to 8.
var zeros = []string{"0", "0", "0", "0", "0", "0", "0", "0"}
// parseIPSlow is like ParseIP, but aims for readability above
// speed. It's the reference implementation for correctness checking
// and against which we measure optimized parsers.
//
// parseIPSlow understands the following forms of IP addresses:
// - Regular IPv4: 1.2.3.4
// - IPv4 with many leading zeros: 0000001.0000002.0000003.0000004
// - Regular IPv6: 1111:2222:3333:4444:5555:6666:7777:8888
// - IPv6 with many leading zeros: 00000001:0000002:0000003:0000004:0000005:0000006:0000007:0000008
// - IPv6 with zero blocks elided: 1111:2222::7777:8888
// - IPv6 with trailing 32 bits expressed as IPv4: 1111:2222:3333:4444:5555:6666:77.77.88.88
//
// It does not process the following IP address forms, which have been
// varyingly accepted by some programs due to an under-specification
// of the shapes of IPv4 addresses:
//
// - IPv4 as a single 32-bit uint: 4660 (same as "1.2.3.4")
// - IPv4 with octal numbers: 0300.0250.0.01 (same as "192.168.0.1")
// - IPv4 with hex numbers: 0xc0.0xa8.0x0.0x1 (same as "192.168.0.1")
// - IPv4 in "class-B style": 1.2.52 (same as "1.2.3.4")
// - IPv4 in "class-A style": 1.564 (same as "1.2.3.4")
func parseIPSlow(s string) (Addr, error) {
// Identify and strip out the zone, if any. There should be 0 or 1
// '%' in the string.
var zone string
fs := strings.Split(s, "%")
switch len(fs) {
case 1:
// No zone, that's fine.
case 2:
s, zone = fs[0], fs[1]
if zone == "" {
return Addr{}, fmt.Errorf("netaddr.ParseIP(%q): no zone after zone specifier", s)
}
default:
return Addr{}, fmt.Errorf("netaddr.ParseIP(%q): too many zone specifiers", s) // TODO: less specific?
}
// IPv4 by itself is easy to do in a helper.
if strings.Count(s, ":") == 0 {
if zone != "" {
return Addr{}, fmt.Errorf("netaddr.ParseIP(%q): IPv4 addresses cannot have a zone", s)
}
return parseIPv4Slow(s)
}
normal, err := normalizeIPv6Slow(s)
if err != nil {
return Addr{}, err
}
// At this point, we've normalized the address back into 8 hex
// fields of 16 bits each. Parse that.
fs = strings.Split(normal, ":")
if len(fs) != 8 {
return Addr{}, fmt.Errorf("netaddr.ParseIP(%q): wrong size address", s)
}
var ret [16]byte
for i, f := range fs {
a, b, err := parseWord(f)
if err != nil {
return Addr{}, err
}
ret[i*2] = a
ret[i*2+1] = b
}
return AddrFrom16(ret).WithZone(zone), nil
}
// normalizeIPv6Slow expands s, which is assumed to be an IPv6
// address, to its canonical text form.
//
// The canonical form of an IPv6 address is 8 colon-separated fields,
// where each field should be a hex value from 0 to ffff. This
// function does not verify the contents of each field.
//
// This function performs two transformations:
// - The last 32 bits of an IPv6 address may be represented in
// IPv4-style dotted quad form, as in 1:2:3:4:5:6:7.8.9.10. That
// address is transformed to its hex equivalent,
// e.g. 1:2:3:4:5:6:708:90a.
// - An address may contain one "::", which expands into as many
// 16-bit blocks of zeros as needed to make the address its correct
// full size. For example, fe80::1:2 expands to fe80:0:0:0:0:0:1:2.
//
// Both short forms may be present in a single address,
// e.g. fe80::1.2.3.4.
func normalizeIPv6Slow(orig string) (string, error) {
s := orig
// Find and convert an IPv4 address in the final field, if any.
i := strings.LastIndex(s, ":")
if i == -1 {
return "", fmt.Errorf("netaddr.ParseIP(%q): invalid IP address", orig)
}
if strings.Contains(s[i+1:], ".") {
ip, err := parseIPv4Slow(s[i+1:])
if err != nil {
return "", err
}
a4 := ip.As4()
s = fmt.Sprintf("%s:%02x%02x:%02x%02x", s[:i], a4[0], a4[1], a4[2], a4[3])
}
// Find and expand a ::, if any.
fs := strings.Split(s, "::")
switch len(fs) {
case 1:
// No ::, nothing to do.
case 2:
lhs, rhs := fs[0], fs[1]
// Found a ::, figure out how many zero blocks need to be
// inserted.
nblocks := strings.Count(lhs, ":") + strings.Count(rhs, ":")
if lhs != "" {
nblocks++
}
if rhs != "" {
nblocks++
}
if nblocks > 7 {
return "", fmt.Errorf("netaddr.ParseIP(%q): address too long", orig)
}
fs = nil
// Either side of the :: can be empty. We don't want empty
// fields to feature in the final normalized address.
if lhs != "" {
fs = append(fs, lhs)
}
fs = append(fs, zeros[:8-nblocks]...)
if rhs != "" {
fs = append(fs, rhs)
}
s = strings.Join(fs, ":")
default:
// Too many ::
return "", fmt.Errorf("netaddr.ParseIP(%q): invalid IP address", orig)
}
return s, nil
}
// parseIPv4Slow parses and returns an IPv4 address in dotted quad
// form, e.g. "192.168.0.1". It is slow but easy to read, and the
// reference implementation against which we compare faster
// implementations for correctness.
func parseIPv4Slow(s string) (Addr, error) {
fs := strings.Split(s, ".")
if len(fs) != 4 {
return Addr{}, fmt.Errorf("netaddr.ParseIP(%q): invalid IP address", s)
}
var ret [4]byte
for i := range ret {
val, err := strconv.ParseUint(fs[i], 10, 8)
if err != nil {
return Addr{}, err
}
ret[i] = uint8(val)
}
return AddrFrom4([4]byte{ret[0], ret[1], ret[2], ret[3]}), nil
}
// parseWord converts a 16-bit hex string into its corresponding
// two-byte value.
func parseWord(s string) (byte, byte, error) {
ret, err := strconv.ParseUint(s, 16, 16)
if err != nil {
return 0, 0, err
}
return uint8(ret >> 8), uint8(ret), nil
}

92
src/net/netip/uint128.go Normal file
View File

@ -0,0 +1,92 @@
// Copyright 2020 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 netip
import "math/bits"
// uint128 represents a uint128 using two uint64s.
//
// When the methods below mention a bit number, bit 0 is the most
// significant bit (in hi) and bit 127 is the lowest (lo&1).
type uint128 struct {
hi uint64
lo uint64
}
// mask6 returns a uint128 bitmask with the topmost n bits of a
// 128-bit number.
func mask6(n int) uint128 {
return uint128{^(^uint64(0) >> n), ^uint64(0) << (128 - n)}
}
// isZero reports whether u == 0.
//
// It's faster than u == (uint128{}) because the compiler (as of Go
// 1.15/1.16b1) doesn't do this trick and instead inserts a branch in
// its eq alg's generated code.
func (u uint128) isZero() bool { return u.hi|u.lo == 0 }
// and returns the bitwise AND of u and m (u&m).
func (u uint128) and(m uint128) uint128 {
return uint128{u.hi & m.hi, u.lo & m.lo}
}
// xor returns the bitwise XOR of u and m (u^m).
func (u uint128) xor(m uint128) uint128 {
return uint128{u.hi ^ m.hi, u.lo ^ m.lo}
}
// or returns the bitwise OR of u and m (u|m).
func (u uint128) or(m uint128) uint128 {
return uint128{u.hi | m.hi, u.lo | m.lo}
}
// not returns the bitwise NOT of u.
func (u uint128) not() uint128 {
return uint128{^u.hi, ^u.lo}
}
// subOne returns u - 1.
func (u uint128) subOne() uint128 {
lo, borrow := bits.Sub64(u.lo, 1, 0)
return uint128{u.hi - borrow, lo}
}
// addOne returns u + 1.
func (u uint128) addOne() uint128 {
lo, carry := bits.Add64(u.lo, 1, 0)
return uint128{u.hi + carry, lo}
}
func u64CommonPrefixLen(a, b uint64) uint8 {
return uint8(bits.LeadingZeros64(a ^ b))
}
func (u uint128) commonPrefixLen(v uint128) (n uint8) {
if n = u64CommonPrefixLen(u.hi, v.hi); n == 64 {
n += u64CommonPrefixLen(u.lo, v.lo)
}
return
}
// halves returns the two uint64 halves of the uint128.
//
// Logically, think of it as returning two uint64s.
// It only returns pointers for inlining reasons on 32-bit platforms.
func (u *uint128) halves() [2]*uint64 {
return [2]*uint64{&u.hi, &u.lo}
}
// bitsSetFrom returns a copy of u with the given bit
// and all subsequent ones set.
func (u uint128) bitsSetFrom(bit uint8) uint128 {
return u.or(mask6(int(bit)).not())
}
// bitsClearedFrom returns a copy of u with the given bit
// and all subsequent ones cleared.
func (u uint128) bitsClearedFrom(bit uint8) uint128 {
return u.and(mask6(int(bit)))
}

View File

@ -0,0 +1,89 @@
// Copyright 2020 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 netip
import (
"testing"
)
func TestUint128AddSub(t *testing.T) {
const add1 = 1
const sub1 = -1
tests := []struct {
in uint128
op int // +1 or -1 to add vs subtract
want uint128
}{
{uint128{0, 0}, add1, uint128{0, 1}},
{uint128{0, 1}, add1, uint128{0, 2}},
{uint128{1, 0}, add1, uint128{1, 1}},
{uint128{0, ^uint64(0)}, add1, uint128{1, 0}},
{uint128{^uint64(0), ^uint64(0)}, add1, uint128{0, 0}},
{uint128{0, 0}, sub1, uint128{^uint64(0), ^uint64(0)}},
{uint128{0, 1}, sub1, uint128{0, 0}},
{uint128{0, 2}, sub1, uint128{0, 1}},
{uint128{1, 0}, sub1, uint128{0, ^uint64(0)}},
{uint128{1, 1}, sub1, uint128{1, 0}},
}
for _, tt := range tests {
var got uint128
switch tt.op {
case add1:
got = tt.in.addOne()
case sub1:
got = tt.in.subOne()
default:
panic("bogus op")
}
if got != tt.want {
t.Errorf("%v add %d = %v; want %v", tt.in, tt.op, got, tt.want)
}
}
}
func TestBitsSetFrom(t *testing.T) {
tests := []struct {
bit uint8
want uint128
}{
{0, uint128{^uint64(0), ^uint64(0)}},
{1, uint128{^uint64(0) >> 1, ^uint64(0)}},
{63, uint128{1, ^uint64(0)}},
{64, uint128{0, ^uint64(0)}},
{65, uint128{0, ^uint64(0) >> 1}},
{127, uint128{0, 1}},
{128, uint128{0, 0}},
}
for _, tt := range tests {
var zero uint128
got := zero.bitsSetFrom(tt.bit)
if got != tt.want {
t.Errorf("0.bitsSetFrom(%d) = %064b want %064b", tt.bit, got, tt.want)
}
}
}
func TestBitsClearedFrom(t *testing.T) {
tests := []struct {
bit uint8
want uint128
}{
{0, uint128{0, 0}},
{1, uint128{1 << 63, 0}},
{63, uint128{^uint64(0) &^ 1, 0}},
{64, uint128{^uint64(0), 0}},
{65, uint128{^uint64(0), 1 << 63}},
{127, uint128{^uint64(0), ^uint64(0) &^ 1}},
{128, uint128{^uint64(0), ^uint64(0)}},
}
for _, tt := range tests {
ones := uint128{^uint64(0), ^uint64(0)}
got := ones.bitsClearedFrom(tt.bit)
if got != tt.want {
t.Errorf("ones.bitsClearedFrom(%d) = %064b want %064b", tt.bit, got, tt.want)
}
}
}

View File

@ -341,26 +341,3 @@ func readFull(r io.Reader) (all []byte, err error) {
}
}
}
// 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 ""
}

View File

@ -51,33 +51,6 @@ func TestReadLine(t *testing.T) {
}
}
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)
}
}
}
func TestDtoi(t *testing.T) {
for _, tt := range []struct {
in string

View File

@ -8,6 +8,7 @@ import (
"context"
"internal/itoa"
"io"
"net/netip"
"os"
"syscall"
"time"
@ -23,6 +24,20 @@ type TCPAddr struct {
Zone string // IPv6 scoped addressing zone
}
// AddrPort returns the TCPAddr a as a netip.AddrPort.
//
// If a.Port does not fit in a uint16, it's silently truncated.
//
// If a is nil, a zero value is returned.
func (a *TCPAddr) AddrPort() netip.AddrPort {
if a == nil {
return netip.AddrPort{}
}
na, _ := netip.AddrFromSlice(a.IP)
na = na.WithZone(a.Zone)
return netip.AddrPortFrom(na, uint16(a.Port))
}
// Network returns the address's network name, "tcp".
func (a *TCPAddr) Network() string { return "tcp" }

View File

@ -7,6 +7,7 @@ package net
import (
"context"
"internal/itoa"
"net/netip"
"syscall"
)
@ -26,6 +27,20 @@ type UDPAddr struct {
Zone string // IPv6 scoped addressing zone
}
// AddrPort returns the UDPAddr a as a netip.AddrPort.
//
// If a.Port does not fit in a uint16, it's silently truncated.
//
// If a is nil, a zero value is returned.
func (a *UDPAddr) AddrPort() netip.AddrPort {
if a == nil {
return netip.AddrPort{}
}
na, _ := netip.AddrFromSlice(a.IP)
na = na.WithZone(a.Zone)
return netip.AddrPortFrom(na, uint16(a.Port))
}
// Network returns the address's network name, "udp".
func (a *UDPAddr) Network() string { return "udp" }
@ -84,6 +99,21 @@ func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
return addrs.forResolve(network, address).(*UDPAddr), nil
}
// UDPAddrFromAddrPort returns addr as a UDPAddr.
//
// If addr is not valid, it returns nil.
func UDPAddrFromAddrPort(addr netip.AddrPort) *UDPAddr {
if !addr.IsValid() {
return nil
}
ip16 := addr.Addr().As16()
return &UDPAddr{
IP: IP(ip16[:]),
Zone: addr.Addr().Zone(),
Port: int(addr.Port()),
}
}
// UDPConn is the implementation of the Conn and PacketConn interfaces
// for UDP network connections.
type UDPConn struct {
@ -148,6 +178,18 @@ func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr,
return
}
// ReadMsgUDPAddrPort is like ReadMsgUDP but returns an netip.AddrPort instead of a UDPAddr.
func (c *UDPConn) ReadMsgUDPAddrPort(b, oob []byte) (n, oobn, flags int, addr netip.AddrPort, err error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
var ua *UDPAddr
n, oobn, flags, ua, err = c.ReadMsgUDP(b, oob)
addr = ua.AddrPort()
return
}
// WriteToUDP acts like WriteTo but takes a UDPAddr.
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) {
if !c.ok() {
@ -160,6 +202,15 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) {
return n, err
}
// WriteToUDPAddrPort acts like WriteTo but takes a netip.AddrPort.
func (c *UDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
return c.WriteToUDP(b, UDPAddrFromAddrPort(addr))
}
// WriteTo implements the PacketConn WriteTo method.
func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) {
if !c.ok() {
@ -195,6 +246,15 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er
return
}
// WriteMsgUDPAddrPort is like WriteMsgUDP but takes a netip.AddrPort instead of a UDPAddr.
func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
return c.WriteMsgUDP(b, oob, UDPAddrFromAddrPort(addr))
}
func newUDPConn(fd *netFD) *UDPConn { return &UDPConn{conn{fd}} }
// DialUDP acts like Dial for UDP networks.

View File

@ -5,7 +5,7 @@
package strings
// Compare returns an integer comparing two strings lexicographically.
// The result will be 0 if a==b, -1 if a < b, and +1 if a > b.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
//
// Compare is included only for symmetry with package bytes.
// It is usually clearer and always faster to use the built-in