Преглед изворни кода

all: adapt to opaque netaddr types

This commit is a mishmash of automated edits using gofmt:

gofmt -r 'netaddr.IPPort{IP: a, Port: b} -> netaddr.IPPortFrom(a, b)' -w .
gofmt -r 'netaddr.IPPrefix{IP: a, Port: b} -> netaddr.IPPrefixFrom(a, b)' -w .

gofmt -r 'a.IP.Is4 -> a.IP().Is4' -w .
gofmt -r 'a.IP.As16 -> a.IP().As16' -w .
gofmt -r 'a.IP.Is6 -> a.IP().Is6' -w .
gofmt -r 'a.IP.As4 -> a.IP().As4' -w .
gofmt -r 'a.IP.String -> a.IP().String' -w .

And regexps:

\w*(.*)\.Port = (.*)  ->  $1 = $1.WithPort($2)
\w*(.*)\.IP = (.*)  ->  $1 = $1.WithIP($2)

And lots of manual fixups.

Signed-off-by: Josh Bleecher Snyder <[email protected]>
Josh Bleecher Snyder пре 4 година
родитељ
комит
25df067dd0
54 измењених фајлова са 358 додато и 371 уклоњено
  1. 3 3
      cmd/hello/hello.go
  2. 2 2
      cmd/tailscale/cli/file.go
  3. 7 7
      cmd/tailscale/cli/up.go
  4. 1 1
      cmd/tailscaled/tailscaled.go
  5. 3 3
      control/controlclient/direct.go
  6. 1 1
      control/controlclient/direct_test.go
  7. 10 11
      disco/disco.go
  8. 2 2
      go.mod
  9. 6 0
      go.sum
  10. 1 1
      internal/deephash/deephash_test.go
  11. 30 36
      ipn/ipnlocal/local.go
  12. 1 1
      ipn/ipnlocal/local_test.go
  13. 1 1
      ipn/ipnlocal/peerapi.go
  14. 1 1
      ipn/ipnserver/server.go
  15. 2 2
      net/dns/manager.go
  16. 4 3
      net/dns/manager_test.go
  17. 2 2
      net/dns/resolver/tsdns_test.go
  18. 9 9
      net/interfaces/interfaces.go
  19. 6 6
      net/netcheck/netcheck.go
  20. 4 8
      net/netstat/netstat_windows.go
  21. 32 30
      net/packet/packet.go
  22. 8 8
      net/packet/tsmp.go
  23. 13 13
      net/portmapper/portmapper.go
  24. 5 5
      net/tsaddr/tsaddr.go
  25. 5 5
      net/tstun/wrap.go
  26. 1 1
      net/tstun/wrap_test.go
  27. 1 1
      tsnet/tsnet.go
  28. 1 1
      tstest/integration/testcontrol/testcontrol.go
  29. 1 1
      tstest/natlab/firewall.go
  30. 3 6
      tstest/natlab/nat.go
  31. 28 28
      tstest/natlab/natlab.go
  32. 13 13
      tstest/natlab/natlab_test.go
  33. 2 2
      types/netmap/netmap_test.go
  34. 1 1
      wf/firewall.go
  35. 1 1
      wgengine/bench/bench.go
  36. 1 1
      wgengine/bench/bench_test.go
  37. 12 14
      wgengine/filter/filter.go
  38. 12 12
      wgengine/filter/filter_test.go
  39. 5 5
      wgengine/filter/match.go
  40. 4 4
      wgengine/filter/tailcfg.go
  41. 7 7
      wgengine/magicsock/legacy.go
  42. 28 31
      wgengine/magicsock/magicsock.go
  43. 6 6
      wgengine/magicsock/magicsock_test.go
  44. 3 3
      wgengine/monitor/monitor_linux.go
  45. 10 10
      wgengine/netstack/netstack.go
  46. 4 4
      wgengine/pendopen.go
  47. 14 17
      wgengine/router/ifconfig_windows.go
  48. 8 8
      wgengine/router/router_linux.go
  49. 9 9
      wgengine/router/router_openbsd.go
  50. 6 6
      wgengine/router/router_userspace_bsd.go
  51. 1 1
      wgengine/router/router_windows.go
  52. 12 12
      wgengine/userspace.go
  53. 1 1
      wgengine/userspace_test.go
  54. 4 4
      wgengine/wgcfg/nmcfg/nmcfg.go

+ 3 - 3
cmd/hello/hello.go

@@ -112,13 +112,13 @@ func tailscaleIP(who *apitype.WhoIsResponse) string {
 		return ""
 	}
 	for _, nodeIP := range who.Node.Addresses {
-		if nodeIP.IP.Is4() && nodeIP.IsSingleIP() {
-			return nodeIP.IP.String()
+		if nodeIP.IP().Is4() && nodeIP.IsSingleIP() {
+			return nodeIP.IP().String()
 		}
 	}
 	for _, nodeIP := range who.Node.Addresses {
 		if nodeIP.IsSingleIP() {
-			return nodeIP.IP.String()
+			return nodeIP.IP().String()
 		}
 	}
 	return ""

+ 2 - 2
cmd/tailscale/cli/file.go

@@ -194,7 +194,7 @@ func discoverPeerAPIBase(ctx context.Context, ipStr string) (base string, lastSe
 	for _, ft := range fts {
 		n := ft.Node
 		for _, a := range n.Addresses {
-			if a.IP != ip {
+			if a.IP() != ip {
 				continue
 			}
 			if n.LastSeen != nil {
@@ -301,7 +301,7 @@ func runCpTargets(ctx context.Context, args []string) error {
 		if detail != "" {
 			detail = "\t" + detail
 		}
-		fmt.Printf("%s\t%s%s\n", n.Addresses[0].IP, n.ComputedName, detail)
+		fmt.Printf("%s\t%s%s\n", n.Addresses[0].IP(), n.ComputedName, detail)
 	}
 	return nil
 }

+ 7 - 7
cmd/tailscale/cli/up.go

@@ -164,10 +164,10 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
 		routes = append(routes, r)
 	}
 	sort.Slice(routes, func(i, j int) bool {
-		if routes[i].Bits != routes[j].Bits {
-			return routes[i].Bits < routes[j].Bits
+		if routes[i].Bits() != routes[j].Bits() {
+			return routes[i].Bits() < routes[j].Bits()
 		}
-		return routes[i].IP.Less(routes[j].IP)
+		return routes[i].IP().Less(routes[j].IP())
 	})
 
 	var exitNodeIP netaddr.IP
@@ -723,10 +723,10 @@ func fmtFlagValueArg(flagName string, val interface{}) string {
 func hasExitNodeRoutes(rr []netaddr.IPPrefix) bool {
 	var v4, v6 bool
 	for _, r := range rr {
-		if r.Bits == 0 {
-			if r.IP.Is4() {
+		if r.Bits() == 0 {
+			if r.IP().Is4() {
 				v4 = true
-			} else if r.IP.Is6() {
+			} else if r.IP().Is6() {
 				v6 = true
 			}
 		}
@@ -743,7 +743,7 @@ func withoutExitNodes(rr []netaddr.IPPrefix) []netaddr.IPPrefix {
 	}
 	var out []netaddr.IPPrefix
 	for _, r := range rr {
-		if r.Bits > 0 {
+		if r.Bits() > 0 {
 			out = append(out, r)
 		}
 	}

+ 1 - 1
cmd/tailscaled/tailscaled.go

@@ -266,7 +266,7 @@ func run() error {
 			if err != nil {
 				return nil, err
 			}
-			if ns != nil && useNetstackForIP(ipp.IP) {
+			if ns != nil && useNetstackForIP(ipp.IP()) {
 				return ns.DialContextTCP(ctx, addr)
 			}
 			var d net.Dialer

+ 3 - 3
control/controlclient/direct.go

@@ -1091,7 +1091,7 @@ func ipForwardingBroken(routes []netaddr.IPPrefix, state *interfaces.State) bool
 	localIPs := map[netaddr.IP]bool{}
 	for _, addrs := range state.InterfaceIPs {
 		for _, pfx := range addrs {
-			localIPs[pfx.IP] = true
+			localIPs[pfx.IP()] = true
 		}
 	}
 
@@ -1100,10 +1100,10 @@ func ipForwardingBroken(routes []netaddr.IPPrefix, state *interfaces.State) bool
 		// It's possible to advertise a route to one of the local
 		// machine's local IPs. IP forwarding isn't required for this
 		// to work, so we shouldn't warn for such exports.
-		if r.IsSingleIP() && localIPs[r.IP] {
+		if r.IsSingleIP() && localIPs[r.IP()] {
 			continue
 		}
-		if r.IP.Is4() {
+		if r.IP().Is4() {
 			v4Routes = true
 		} else {
 			v6Routes = true

+ 1 - 1
control/controlclient/direct_test.go

@@ -86,7 +86,7 @@ func TestNewDirect(t *testing.T) {
 func fakeEndpoints(ports ...uint16) (ret []tailcfg.Endpoint) {
 	for _, port := range ports {
 		ret = append(ret, tailcfg.Endpoint{
-			Addr: netaddr.IPPort{Port: port},
+			Addr: netaddr.IPPortFrom(netaddr.IP{}, port),
 		})
 	}
 	return

+ 10 - 11
disco/disco.go

@@ -147,9 +147,9 @@ const epLength = 16 + 2 // 16 byte IP address + 2 byte port
 func (m *CallMeMaybe) AppendMarshal(b []byte) []byte {
 	ret, p := appendMsgHeader(b, TypeCallMeMaybe, v0, epLength*len(m.MyNumber))
 	for _, ipp := range m.MyNumber {
-		a := ipp.IP.As16()
+		a := ipp.IP().As16()
 		copy(p[:], a[:])
-		binary.BigEndian.PutUint16(p[16:], ipp.Port)
+		binary.BigEndian.PutUint16(p[16:], ipp.Port())
 		p = p[epLength:]
 	}
 	return ret
@@ -164,10 +164,9 @@ func parseCallMeMaybe(ver uint8, p []byte) (m *CallMeMaybe, err error) {
 	for len(p) > 0 {
 		var a [16]byte
 		copy(a[:], p)
-		m.MyNumber = append(m.MyNumber, netaddr.IPPort{
-			IP:   netaddr.IPFrom16(a),
-			Port: binary.BigEndian.Uint16(p[16:18]),
-		})
+		m.MyNumber = append(m.MyNumber, netaddr.IPPortFrom(
+			netaddr.IPFrom16(a),
+			binary.BigEndian.Uint16(p[16:18])))
 		p = p[epLength:]
 	}
 	return m, nil
@@ -187,9 +186,9 @@ const pongLen = 12 + 16 + 2
 func (m *Pong) AppendMarshal(b []byte) []byte {
 	ret, d := appendMsgHeader(b, TypePong, v0, pongLen)
 	d = d[copy(d, m.TxID[:]):]
-	ip16 := m.Src.IP.As16()
+	ip16 := m.Src.IP().As16()
 	d = d[copy(d, ip16[:]):]
-	binary.BigEndian.PutUint16(d, m.Src.Port)
+	binary.BigEndian.PutUint16(d, m.Src.Port())
 	return ret
 }
 
@@ -201,10 +200,10 @@ func parsePong(ver uint8, p []byte) (m *Pong, err error) {
 	copy(m.TxID[:], p)
 	p = p[12:]
 
-	m.Src.IP, _ = netaddr.FromStdIP(net.IP(p[:16]))
+	srcIP, _ := netaddr.FromStdIP(net.IP(p[:16]))
 	p = p[16:]
-
-	m.Src.Port = binary.BigEndian.Uint16(p)
+	port := binary.BigEndian.Uint16(p)
+	m.Src = netaddr.IPPortFrom(srcIP, port)
 	return m, nil
 }
 

+ 2 - 2
go.mod

@@ -40,10 +40,10 @@ require (
 	golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8
 	gopkg.in/yaml.v2 v2.2.8 // indirect
 	honnef.co/go/tools v0.1.0
-	inet.af/netaddr v0.0.0-20210511181906-37180328850c
+	inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841
 	inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
 	inet.af/peercred v0.0.0-20210302202138-56e694897155
-	inet.af/wf v0.0.0-20210424212123-eaa011a774a4
+	inet.af/wf v0.0.0-20210516214145-a5343001b756
 	rsc.io/goversion v1.2.0
 )
 

+ 6 - 0
go.sum

@@ -258,11 +258,17 @@ inet.af/netaddr v0.0.0-20210508014949-da1c2a70a83d h1:9tuJMxDV7THGfXWirKBD/v9rbs
 inet.af/netaddr v0.0.0-20210508014949-da1c2a70a83d/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
 inet.af/netaddr v0.0.0-20210511181906-37180328850c h1:rzDy/tC8LjEdN94+i0Bu22tTo/qE9cvhKyfD0HMU0NU=
 inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
+inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841 h1:2HpK+rC0Arcu98JukIlyVfEaE2OsvtmBFc8rs/2SJYs=
+inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
 inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJdiRV8SaxeigOy0q1gg=
 inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
 inet.af/peercred v0.0.0-20210302202138-56e694897155 h1:KojYNEYqDkZ2O3LdyTstR1l13L3ePKTIEM2h7ONkfkE=
 inet.af/peercred v0.0.0-20210302202138-56e694897155/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
 inet.af/wf v0.0.0-20210424212123-eaa011a774a4 h1:g1VVXY1xRKoO17aKY3g9KeJxDW0lGx1n2Y+WPSWkOL8=
 inet.af/wf v0.0.0-20210424212123-eaa011a774a4/go.mod h1:56/0QVlZ4NmPRh1QuU2OfrKqjSgt5P39R534gD2JMpQ=
+inet.af/wf v0.0.0-20210515021317-09f8efa8ac30 h1:TLxVVv7rmErJW7l81tbbR2BkOIYBI3YdxbJbEs/HJt8=
+inet.af/wf v0.0.0-20210515021317-09f8efa8ac30/go.mod h1:ViGMZRA6+RA318D7GCncrjv5gHUrPYrNDejjU12tikA=
+inet.af/wf v0.0.0-20210516214145-a5343001b756 h1:muIT3C1rH3/xpvIH8blKkMvhctV7F+OtZqs7kcwHDBQ=
+inet.af/wf v0.0.0-20210516214145-a5343001b756/go.mod h1:ViGMZRA6+RA318D7GCncrjv5gHUrPYrNDejjU12tikA=
 rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
 rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=

+ 1 - 1
internal/deephash/deephash_test.go

@@ -33,7 +33,7 @@ func getVal() []interface{} {
 	return []interface{}{
 		&wgcfg.Config{
 			Name:      "foo",
-			Addresses: []netaddr.IPPrefix{{Bits: 5, IP: netaddr.IPFrom16([16]byte{3: 3})}},
+			Addresses: []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPFrom16([16]byte{3: 3}), 5)},
 			Peers: []wgcfg.Peer{
 				{
 					Endpoints: wgcfg.Endpoints{

+ 30 - 36
ipn/ipnlocal/local.go

@@ -356,14 +356,14 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
 		var tailAddr4 string
 		var tailscaleIPs = make([]netaddr.IP, 0, len(p.Addresses))
 		for _, addr := range p.Addresses {
-			if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
-				if addr.IP.Is4() && tailAddr4 == "" {
+			if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP()) {
+				if addr.IP().Is4() && tailAddr4 == "" {
 					// The peer struct previously only allowed a single
 					// Tailscale IP address. For compatibility for a few releases starting
 					// with 1.8, keep it pulled out as IPv4-only for a bit.
-					tailAddr4 = addr.IP.String()
+					tailAddr4 = addr.IP().String()
 				}
-				tailscaleIPs = append(tailscaleIPs, addr.IP)
+				tailscaleIPs = append(tailscaleIPs, addr.IP())
 			}
 		}
 		sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
@@ -390,10 +390,10 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
 func (b *LocalBackend) WhoIs(ipp netaddr.IPPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
 	b.mu.Lock()
 	defer b.mu.Unlock()
-	n, ok = b.nodeByAddr[ipp.IP]
+	n, ok = b.nodeByAddr[ipp.IP()]
 	if !ok {
 		var ip netaddr.IP
-		if ipp.Port != 0 {
+		if ipp.Port() != 0 {
 			ip, ok = b.e.WhoIsIPPort(ipp)
 		}
 		if !ok {
@@ -552,7 +552,7 @@ func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged
 
 	for _, peer := range nm.Peers {
 		for _, addr := range peer.Addresses {
-			if !addr.IsSingleIP() || addr.IP != b.prefs.ExitNodeIP {
+			if !addr.IsSingleIP() || addr.IP() != b.prefs.ExitNodeIP {
 				continue
 			}
 			// Found the node being referenced, upgrade prefs to
@@ -891,7 +891,7 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
 	}
 	if prefs != nil {
 		for _, r := range prefs.AdvertiseRoutes {
-			if r.Bits == 0 {
+			if r.Bits() == 0 {
 				// When offering a default route to the world, we
 				// filter out locally reachable LANs, so that the
 				// default route effectively appears to be a "guest
@@ -959,13 +959,13 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
 func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
 	var b netaddr.IPSetBuilder
 	if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
-		if tsaddr.IsTailscaleIP(pfx.IP) {
+		if tsaddr.IsTailscaleIP(pfx.IP()) {
 			return
 		}
 		if pfx.IsSingleIP() {
 			return
 		}
-		hostIPs = append(hostIPs, pfx.IP)
+		hostIPs = append(hostIPs, pfx.IP())
 		b.AddPrefix(pfx)
 	}); err != nil {
 		return nil, nil, err
@@ -1751,10 +1751,10 @@ func (b *LocalBackend) authReconfig() {
 				// https://github.com/tailscale/tailscale/issues/1152
 				// tracks adding the right capability reporting to
 				// enable AAAA in MagicDNS.
-				if addr.IP.Is6() {
+				if addr.IP().Is6() {
 					continue
 				}
-				ips = append(ips, addr.IP)
+				ips = append(ips, addr.IP())
 			}
 			dcfg.Hosts[fqdn] = ips
 		}
@@ -1809,10 +1809,7 @@ func parseResolver(cfg tailcfg.DNSResolver) (netaddr.IPPort, error) {
 	if err != nil {
 		return netaddr.IPPort{}, fmt.Errorf("[unexpected] non-IP resolver %q", cfg.Addr)
 	}
-	return netaddr.IPPort{
-		IP:   ip,
-		Port: 53,
-	}, nil
+	return netaddr.IPPortFrom(ip, 53), nil
 }
 
 // tailscaleVarRoot returns the root directory of Tailscale's writable
@@ -1870,7 +1867,7 @@ func (b *LocalBackend) initPeerAPIListener() {
 	if len(b.netMap.Addresses) == len(b.peerAPIListeners) {
 		allSame := true
 		for i, pln := range b.peerAPIListeners {
-			if pln.ip != b.netMap.Addresses[i].IP {
+			if pln.ip != b.netMap.Addresses[i].IP() {
 				allSame = false
 				break
 			}
@@ -1915,7 +1912,7 @@ func (b *LocalBackend) initPeerAPIListener() {
 		var err error
 		skipListen := i > 0 && isNetstack
 		if !skipListen {
-			ln, err = ps.listen(a.IP, b.prevIfState)
+			ln, err = ps.listen(a.IP(), b.prevIfState)
 			if err != nil {
 				if runtime.GOOS == "windows" {
 					// Expected for now. See Issue 1620.
@@ -1929,7 +1926,7 @@ func (b *LocalBackend) initPeerAPIListener() {
 		}
 		pln := &peerAPIListener{
 			ps: ps,
-			ip: a.IP,
+			ip: a.IP(),
 			ln: ln, // nil for 2nd+ on netstack
 			lb: b,
 		}
@@ -1938,7 +1935,7 @@ func (b *LocalBackend) initPeerAPIListener() {
 		} else {
 			pln.port = ln.Addr().(*net.TCPAddr).Port
 		}
-		pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.port))
+		pln.urlStr = "http://" + net.JoinHostPort(a.IP().String(), strconv.Itoa(pln.port))
 		b.logf("peerapi: serving on %s", pln.urlStr)
 		go pln.serve()
 		b.peerAPIListeners = append(b.peerAPIListeners, pln)
@@ -1989,14 +1986,14 @@ func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPref
 		for _, aip := range peer.AllowedIPs {
 			aip = unmapIPPrefix(aip)
 			// Only add the Tailscale IPv6 ULA once, if we see anybody using part of it.
-			if aip.IP.Is6() && aip.IsSingleIP() && tsULA.Contains(aip.IP) {
+			if aip.IP().Is6() && aip.IsSingleIP() && tsULA.Contains(aip.IP()) {
 				if !didULA {
 					didULA = true
 					routes = append(routes, tsULA)
 				}
 				continue
 			}
-			if aip.IsSingleIP() && cgNAT.Contains(aip.IP) {
+			if aip.IsSingleIP() && cgNAT.Contains(aip.IP()) {
 				cgNATIPs = append(cgNATIPs, aip)
 			} else {
 				routes = append(routes, aip)
@@ -2063,16 +2060,13 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router
 		}
 	}
 
-	rs.Routes = append(rs.Routes, netaddr.IPPrefix{
-		IP:   tsaddr.TailscaleServiceIP(),
-		Bits: 32,
-	})
+	rs.Routes = append(rs.Routes, netaddr.IPPrefixFrom(tsaddr.TailscaleServiceIP(), 32))
 
 	return rs
 }
 
 func unmapIPPrefix(ipp netaddr.IPPrefix) netaddr.IPPrefix {
-	return netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits}
+	return netaddr.IPPrefixFrom(ipp.IP().Unmap(), ipp.Bits())
 }
 
 func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
@@ -2156,7 +2150,7 @@ func (b *LocalBackend) enterState(newState ipn.State) {
 	case ipn.Running:
 		var addrs []string
 		for _, addr := range b.netMap.Addresses {
-			addrs = append(addrs, addr.IP.String())
+			addrs = append(addrs, addr.IP().String())
 		}
 		systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " "))
 	default:
@@ -2424,7 +2418,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
 	addNode := func(n *tailcfg.Node) {
 		for _, ipp := range n.Addresses {
 			if ipp.IsSingleIP() {
-				b.nodeByAddr[ipp.IP] = n
+				b.nodeByAddr[ipp.IP()] = n
 			}
 		}
 	}
@@ -2576,9 +2570,9 @@ func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
 			continue
 		}
 		switch {
-		case a.IP.Is4():
+		case a.IP().Is4():
 			have4 = true
-		case a.IP.Is6():
+		case a.IP().Is6():
 			have6 = true
 		}
 	}
@@ -2594,11 +2588,11 @@ func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
 	var ipp netaddr.IPPort
 	switch {
 	case have4 && p4 != 0:
-		ipp = netaddr.IPPort{IP: nodeIP(peer, netaddr.IP.Is4), Port: p4}
+		ipp = netaddr.IPPortFrom(nodeIP(peer, netaddr.IP.Is4), p4)
 	case have6 && p6 != 0:
-		ipp = netaddr.IPPort{IP: nodeIP(peer, netaddr.IP.Is6), Port: p6}
+		ipp = netaddr.IPPortFrom(nodeIP(peer, netaddr.IP.Is6), p6)
 	}
-	if ipp.IP.IsZero() {
+	if ipp.IP().IsZero() {
 		return ""
 	}
 	return fmt.Sprintf("http://%v", ipp)
@@ -2606,8 +2600,8 @@ func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
 
 func nodeIP(n *tailcfg.Node, pred func(netaddr.IP) bool) netaddr.IP {
 	for _, a := range n.Addresses {
-		if a.IsSingleIP() && pred(a.IP) {
-			return a.IP
+		if a.IsSingleIP() && pred(a.IP()) {
+			return a.IP()
 		}
 	}
 	return netaddr.IP{}

+ 1 - 1
ipn/ipnlocal/local_test.go

@@ -171,7 +171,7 @@ func TestShrinkDefaultRoute(t *testing.T) {
 			out: []string{
 				"fe80::1",
 				"ff00::1",
-				tsaddr.TailscaleULARange().IP.String(),
+				tsaddr.TailscaleULARange().IP().String(),
 			},
 			localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is6() },
 		},

+ 1 - 1
ipn/ipnlocal/peerapi.go

@@ -510,7 +510,7 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 <body>
 <h1>Hello, %s (%v)</h1>
 This is my Tailscale device. Your device is %v.
-`, html.EscapeString(who), h.remoteAddr.IP, html.EscapeString(h.peerNode.ComputedName))
+`, html.EscapeString(who), h.remoteAddr.IP(), html.EscapeString(h.peerNode.ComputedName))
 
 	if h.isSelf {
 		fmt.Fprintf(w, "<p>You are the owner of this node.\n")

+ 1 - 1
ipn/ipnserver/server.go

@@ -147,7 +147,7 @@ func (s *server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
 	if err != nil {
 		return ci, fmt.Errorf("parsing local remote: %w", err)
 	}
-	if !la.IP.IsLoopback() || !ra.IP.IsLoopback() {
+	if !la.IP().IsLoopback() || !ra.IP().IsLoopback() {
 		return ci, errors.New("non-loopback connection")
 	}
 	tab, err := netstat.Get()

+ 2 - 2
net/dns/manager.go

@@ -211,7 +211,7 @@ func (m *Manager) compileConfig(cfg Config) (resolver.Config, OSConfig, error) {
 func toIPsOnly(ipps []netaddr.IPPort) (ret []netaddr.IP) {
 	ret = make([]netaddr.IP, 0, len(ipps))
 	for _, ipp := range ipps {
-		ret = append(ret, ipp.IP)
+		ret = append(ret, ipp.IP())
 	}
 	return ret
 }
@@ -219,7 +219,7 @@ func toIPsOnly(ipps []netaddr.IPPort) (ret []netaddr.IP) {
 func toIPPorts(ips []netaddr.IP) (ret []netaddr.IPPort) {
 	ret = make([]netaddr.IPPort, 0, len(ips))
 	for _, ip := range ips {
-		ret = append(ret, netaddr.IPPort{IP: ip, Port: 53})
+		ret = append(ret, netaddr.IPPortFrom(ip, 53))
 	}
 	return ret
 }

+ 4 - 3
net/dns/manager_test.go

@@ -368,11 +368,12 @@ func TestManager(t *testing.T) {
 			if err := m.Set(test.in); err != nil {
 				t.Fatalf("m.Set: %v", err)
 			}
-			tr := cmp.Transformer("ipStr", func(ip netaddr.IP) string { return ip.String() })
-			if diff := cmp.Diff(f.OSConfig, test.os, tr, cmpopts.EquateEmpty()); diff != "" {
+			trIP := cmp.Transformer("ipStr", func(ip netaddr.IP) string { return ip.String() })
+			trIPPort := cmp.Transformer("ippStr", func(ipp netaddr.IPPort) string { return ipp.String() })
+			if diff := cmp.Diff(f.OSConfig, test.os, trIP, trIPPort, cmpopts.EquateEmpty()); diff != "" {
 				t.Errorf("wrong OSConfig (-got+want)\n%s", diff)
 			}
-			if diff := cmp.Diff(f.ResolverConfig, test.rs, tr, cmpopts.EquateEmpty()); diff != "" {
+			if diff := cmp.Diff(f.ResolverConfig, test.rs, trIP, trIPPort, cmpopts.EquateEmpty()); diff != "" {
 				t.Errorf("wrong resolver.Config (-got+want)\n%s", diff)
 			}
 		})

+ 2 - 2
net/dns/resolver/tsdns_test.go

@@ -433,8 +433,8 @@ func TestDelegateCollision(t *testing.T) {
 		qtype dns.Type
 		addr  netaddr.IPPort
 	}{
-		{"test.site.", dns.TypeA, netaddr.IPPort{IP: netaddr.IPv4(1, 1, 1, 1), Port: 1001}},
-		{"test.site.", dns.TypeAAAA, netaddr.IPPort{IP: netaddr.IPv4(1, 1, 1, 1), Port: 1002}},
+		{"test.site.", dns.TypeA, netaddr.IPPortFrom(netaddr.IPv4(1, 1, 1, 1), 1001)},
+		{"test.site.", dns.TypeAAAA, netaddr.IPPortFrom(netaddr.IPv4(1, 1, 1, 1), 1002)},
 	}
 
 	// packets will have the same dns txid.

+ 9 - 9
net/interfaces/interfaces.go

@@ -195,7 +195,7 @@ func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
 			}
 		}
 		sort.Slice(pfxs, func(i, j int) bool {
-			return pfxs[i].IP.Less(pfxs[j].IP)
+			return pfxs[i].IP().Less(pfxs[j].IP())
 		})
 		fn(Interface{iface}, pfxs)
 	}
@@ -264,7 +264,7 @@ func (s *State) String() string {
 			fmt.Fprintf(&sb, "%s:[", ifName)
 			needSpace := false
 			for _, pfx := range s.InterfaceIPs[ifName] {
-				if !isInterestingIP(pfx.IP) {
+				if !isInterestingIP(pfx.IP()) {
 					continue
 				}
 				if needSpace {
@@ -367,7 +367,7 @@ func (s *State) AnyInterfaceUp() bool {
 
 func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
 	for _, pfx := range pfxs {
-		if tsaddr.IsTailscaleIP(pfx.IP) {
+		if tsaddr.IsTailscaleIP(pfx.IP()) {
 			return true
 		}
 	}
@@ -407,11 +407,11 @@ func GetState() (*State, error) {
 			return
 		}
 		for _, pfx := range pfxs {
-			if pfx.IP.IsLoopback() || pfx.IP.IsLinkLocalUnicast() {
+			if pfx.IP().IsLoopback() || pfx.IP().IsLinkLocalUnicast() {
 				continue
 			}
-			s.HaveV6Global = s.HaveV6Global || isGlobalV6(pfx.IP)
-			s.HaveV4 = s.HaveV4 || pfx.IP.Is4()
+			s.HaveV6Global = s.HaveV6Global || isGlobalV6(pfx.IP())
+			s.HaveV4 = s.HaveV4 || pfx.IP().Is4()
 		}
 	}); err != nil {
 		return nil, err
@@ -447,7 +447,7 @@ func HTTPOfListener(ln net.Listener) string {
 	var goodIP string
 	var privateIP string
 	ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
-		ip := pfx.IP
+		ip := pfx.IP()
 		if isPrivateIP(ip) {
 			if privateIP == "" {
 				privateIP = ip.String()
@@ -484,7 +484,7 @@ func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) {
 		return
 	}
 	ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
-		ip := pfx.IP
+		ip := pfx.IP()
 		if !i.IsUp() || ip.IsZero() || !myIP.IsZero() {
 			return
 		}
@@ -528,7 +528,7 @@ var (
 // isInterestingIP.
 func anyInterestingIP(pfxs []netaddr.IPPrefix) bool {
 	for _, pfx := range pfxs {
-		if isInterestingIP(pfx.IP) {
+		if isInterestingIP(pfx.IP()) {
 			return true
 		}
 	}

+ 6 - 6
net/netcheck/netcheck.go

@@ -625,7 +625,7 @@ func (rs *reportState) stopTimers() {
 func (rs *reportState) addNodeLatency(node *tailcfg.DERPNode, ipp netaddr.IPPort, d time.Duration) {
 	var ipPortStr string
 	if ipp != (netaddr.IPPort{}) {
-		ipPortStr = net.JoinHostPort(ipp.IP.String(), fmt.Sprint(ipp.Port))
+		ipPortStr = net.JoinHostPort(ipp.IP().String(), fmt.Sprint(ipp.Port()))
 	}
 
 	rs.mu.Lock()
@@ -650,13 +650,13 @@ func (rs *reportState) addNodeLatency(node *tailcfg.DERPNode, ipp netaddr.IPPort
 	}
 
 	switch {
-	case ipp.IP.Is6():
+	case ipp.IP().Is6():
 		updateLatency(ret.RegionV6Latency, node.RegionID, d)
 		ret.IPv6 = true
 		ret.GlobalV6 = ipPortStr
 		// TODO: track MappingVariesByDestIP for IPv6
 		// too? Would be sad if so, but who knows.
-	case ipp.IP.Is4():
+	case ipp.IP().Is4():
 		updateLatency(ret.RegionV4Latency, node.RegionID, d)
 		ret.IPv4 = true
 		if rs.gotEP4 == "" {
@@ -1172,7 +1172,7 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
 		if proto == probeIPv6 && ip.Is4() {
 			return nil
 		}
-		return netaddr.IPPort{IP: ip, Port: uint16(port)}.UDPAddr()
+		return netaddr.IPPortFrom(ip, uint16(port)).UDPAddr()
 	}
 
 	switch proto {
@@ -1182,7 +1182,7 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
 			if !ip.Is4() {
 				return nil
 			}
-			return netaddr.IPPort{IP: ip, Port: uint16(port)}.UDPAddr()
+			return netaddr.IPPortFrom(ip, uint16(port)).UDPAddr()
 		}
 	case probeIPv6:
 		if n.IPv6 != "" {
@@ -1190,7 +1190,7 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
 			if !ip.Is6() {
 				return nil
 			}
-			return netaddr.IPPort{IP: ip, Port: uint16(port)}.UDPAddr()
+			return netaddr.IPPortFrom(ip, uint16(port)).UDPAddr()
 		}
 	default:
 		return nil

+ 4 - 8
net/netstat/netstat_windows.go

@@ -157,10 +157,9 @@ func ipport4(addr uint32, port uint16) netaddr.IPPort {
 	if !endian.Big {
 		addr = bits.ReverseBytes32(addr)
 	}
-	return netaddr.IPPort{
-		IP:   netaddr.IPv4(byte(addr>>24), byte(addr>>16), byte(addr>>8), byte(addr)),
-		Port: port,
-	}
+	return netaddr.IPPortFrom(
+		netaddr.IPv4(byte(addr>>24), byte(addr>>16), byte(addr>>8), byte(addr)),
+		port)
 }
 
 func ipport6(addr [16]byte, scope uint32, port uint16) netaddr.IPPort {
@@ -169,10 +168,7 @@ func ipport6(addr [16]byte, scope uint32, port uint16) netaddr.IPPort {
 		// TODO: something better here?
 		ip = ip.WithZone(fmt.Sprint(scope))
 	}
-	return netaddr.IPPort{
-		IP:   ip,
-		Port: port,
-	}
+	return netaddr.IPPortFrom(ip, port)
 }
 
 func port(v *uint32) uint16 {

+ 32 - 30
net/packet/packet.go

@@ -76,8 +76,8 @@ func (p *Parsed) String() string {
 //
 // TODO: make netaddr more efficient in this area, and retire this func.
 func writeIPPort(sb *strbuilder.Builder, ipp netaddr.IPPort) {
-	if ipp.IP.Is4() {
-		raw := ipp.IP.As4()
+	if ipp.IP().Is4() {
+		raw := ipp.IP().As4()
 		sb.WriteUint(uint64(raw[0]))
 		sb.WriteByte('.')
 		sb.WriteUint(uint64(raw[1]))
@@ -88,10 +88,10 @@ func writeIPPort(sb *strbuilder.Builder, ipp netaddr.IPPort) {
 		sb.WriteByte(':')
 	} else {
 		sb.WriteByte('[')
-		sb.WriteString(ipp.IP.String()) // TODO: faster?
+		sb.WriteString(ipp.IP().String()) // TODO: faster?
 		sb.WriteString("]:")
 	}
-	sb.WriteUint(uint64(ipp.Port))
+	sb.WriteUint(uint64(ipp.Port()))
 }
 
 // Decode extracts data from the packet in b into q.
@@ -142,8 +142,8 @@ func (q *Parsed) decode4(b []byte) {
 	}
 
 	// If it's valid IPv4, then the IP addresses are valid
-	q.Src.IP = netaddr.IPv4(b[12], b[13], b[14], b[15])
-	q.Dst.IP = netaddr.IPv4(b[16], b[17], b[18], b[19])
+	q.Src = q.Src.WithIP(netaddr.IPv4(b[12], b[13], b[14], b[15]))
+	q.Dst = q.Dst.WithIP(netaddr.IPv4(b[16], b[17], b[18], b[19]))
 
 	q.subofs = int((b[0] & 0x0F) << 2)
 	if q.subofs > q.length {
@@ -185,8 +185,8 @@ func (q *Parsed) decode4(b []byte) {
 				q.IPProto = unknown
 				return
 			}
-			q.Src.Port = 0
-			q.Dst.Port = 0
+			q.Src = q.Src.WithPort(0)
+			q.Dst = q.Dst.WithPort(0)
 			q.dataofs = q.subofs + icmp4HeaderLength
 			return
 		case ipproto.IGMP:
@@ -198,8 +198,8 @@ func (q *Parsed) decode4(b []byte) {
 				q.IPProto = unknown
 				return
 			}
-			q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
-			q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+			q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
+			q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
 			q.TCPFlags = TCPFlag(sub[13]) & 0x3F
 			headerLength := (sub[12] & 0xF0) >> 2
 			q.dataofs = q.subofs + int(headerLength)
@@ -209,8 +209,8 @@ func (q *Parsed) decode4(b []byte) {
 				q.IPProto = unknown
 				return
 			}
-			q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
-			q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+			q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
+			q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
 			q.dataofs = q.subofs + udpHeaderLength
 			return
 		case ipproto.SCTP:
@@ -218,8 +218,8 @@ func (q *Parsed) decode4(b []byte) {
 				q.IPProto = unknown
 				return
 			}
-			q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
-			q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+			q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
+			q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
 			return
 		case ipproto.TSMP:
 			// Inter-tailscale messages.
@@ -265,8 +265,10 @@ func (q *Parsed) decode6(b []byte) {
 
 	// okay to ignore `ok` here, because IPs pulled from packets are
 	// always well-formed stdlib IPs.
-	q.Src.IP, _ = netaddr.FromStdIP(net.IP(b[8:24]))
-	q.Dst.IP, _ = netaddr.FromStdIP(net.IP(b[24:40]))
+	srcIP, _ := netaddr.FromStdIP(net.IP(b[8:24]))
+	dstIP, _ := netaddr.FromStdIP(net.IP(b[24:40]))
+	q.Src = q.Src.WithIP(srcIP)
+	q.Dst = q.Dst.WithIP(dstIP)
 
 	// We don't support any IPv6 extension headers. Don't try to
 	// be clever. Therefore, the IP subprotocol always starts at
@@ -290,16 +292,16 @@ func (q *Parsed) decode6(b []byte) {
 			q.IPProto = unknown
 			return
 		}
-		q.Src.Port = 0
-		q.Dst.Port = 0
+		q.Src = q.Src.WithPort(0)
+		q.Dst = q.Dst.WithPort(0)
 		q.dataofs = q.subofs + icmp6HeaderLength
 	case ipproto.TCP:
 		if len(sub) < tcpHeaderLength {
 			q.IPProto = unknown
 			return
 		}
-		q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
-		q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+		q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
+		q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
 		q.TCPFlags = TCPFlag(sub[13]) & 0x3F
 		headerLength := (sub[12] & 0xF0) >> 2
 		q.dataofs = q.subofs + int(headerLength)
@@ -309,16 +311,16 @@ func (q *Parsed) decode6(b []byte) {
 			q.IPProto = unknown
 			return
 		}
-		q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
-		q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+		q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
+		q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
 		q.dataofs = q.subofs + udpHeaderLength
 	case ipproto.SCTP:
 		if len(sub) < sctpHeaderLength {
 			q.IPProto = unknown
 			return
 		}
-		q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
-		q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+		q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
+		q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
 		return
 	case ipproto.TSMP:
 		// Inter-tailscale messages.
@@ -338,8 +340,8 @@ func (q *Parsed) IP4Header() IP4Header {
 	return IP4Header{
 		IPID:    ipid,
 		IPProto: q.IPProto,
-		Src:     q.Src.IP,
-		Dst:     q.Dst.IP,
+		Src:     q.Src.IP(),
+		Dst:     q.Dst.IP(),
 	}
 }
 
@@ -351,8 +353,8 @@ func (q *Parsed) IP6Header() IP6Header {
 	return IP6Header{
 		IPID:    ipid,
 		IPProto: q.IPProto,
-		Src:     q.Src.IP,
-		Dst:     q.Dst.IP,
+		Src:     q.Src.IP(),
+		Dst:     q.Dst.IP(),
 	}
 }
 
@@ -373,8 +375,8 @@ func (q *Parsed) UDP4Header() UDP4Header {
 	}
 	return UDP4Header{
 		IP4Header: q.IP4Header(),
-		SrcPort:   q.Src.Port,
-		DstPort:   q.Dst.Port,
+		SrcPort:   q.Src.Port(),
+		DstPort:   q.Dst.Port(),
 	}
 }
 

+ 8 - 8
net/packet/tsmp.go

@@ -143,7 +143,7 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
 	if len(buf) > maxPacketLength {
 		return errLargePacket
 	}
-	if h.Src.IP.Is4() {
+	if h.Src.IP().Is4() {
 		iph := IP4Header{
 			IPProto: ipproto.TSMP,
 			Src:     h.IPSrc,
@@ -151,7 +151,7 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
 		}
 		iph.Marshal(buf)
 		buf = buf[ip4HeaderLength:]
-	} else if h.Src.IP.Is6() {
+	} else if h.Src.IP().Is6() {
 		iph := IP6Header{
 			IPProto: ipproto.TSMP,
 			Src:     h.IPSrc,
@@ -165,8 +165,8 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
 	buf[0] = byte(TSMPTypeRejectedConn)
 	buf[1] = byte(h.Proto)
 	buf[2] = byte(h.Reason)
-	binary.BigEndian.PutUint16(buf[3:5], h.Src.Port)
-	binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port)
+	binary.BigEndian.PutUint16(buf[3:5], h.Src.Port())
+	binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port())
 
 	if h.hasFlags() {
 		var flags byte
@@ -190,10 +190,10 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
 	h = TailscaleRejectedHeader{
 		Proto:  ipproto.Proto(p[1]),
 		Reason: TailscaleRejectReason(p[2]),
-		IPSrc:  pp.Src.IP,
-		IPDst:  pp.Dst.IP,
-		Src:    netaddr.IPPort{IP: pp.Dst.IP, Port: binary.BigEndian.Uint16(p[3:5])},
-		Dst:    netaddr.IPPort{IP: pp.Src.IP, Port: binary.BigEndian.Uint16(p[5:7])},
+		IPSrc:  pp.Src.IP(),
+		IPDst:  pp.Dst.IP(),
+		Src:    netaddr.IPPortFrom(pp.Dst.IP(), binary.BigEndian.Uint16(p[3:5])),
+		Dst:    netaddr.IPPortFrom(pp.Src.IP(), binary.BigEndian.Uint16(p[5:7])),
 	}
 	if len(p) > 7 {
 		flags := p[7]

+ 13 - 13
net/portmapper/portmapper.go

@@ -84,7 +84,7 @@ type pmpMapping struct {
 
 // externalValid reports whether m.external is valid, with both its IP and Port populated.
 func (m *pmpMapping) externalValid() bool {
-	return !m.external.IP.IsZero() && m.external.Port != 0
+	return !m.external.IP().IsZero() && m.external.Port() != 0
 }
 
 // release does a best effort fire-and-forget release of the PMP mapping m.
@@ -94,8 +94,8 @@ func (m *pmpMapping) release() {
 		return
 	}
 	defer uc.Close()
-	pkt := buildPMPRequestMappingPacket(m.internal.Port, m.external.Port, pmpMapLifetimeDelete)
-	uc.WriteTo(pkt, netaddr.IPPort{IP: m.gw, Port: pmpPort}.UDPAddr())
+	pkt := buildPMPRequestMappingPacket(m.internal.Port(), m.external.Port(), pmpMapLifetimeDelete)
+	uc.WriteTo(pkt, netaddr.IPPortFrom(m.gw, pmpPort).UDPAddr())
 }
 
 // NewClient returns a new portmapping client.
@@ -256,7 +256,7 @@ func (c *Client) CreateOrGetMapping(ctx context.Context) (external netaddr.IPPor
 	localPort := c.localPort
 	m := &pmpMapping{
 		gw:       gw,
-		internal: netaddr.IPPort{IP: myIP, Port: localPort},
+		internal: netaddr.IPPortFrom(myIP, localPort),
 	}
 
 	// prevPort is the port we had most previously, if any. We try
@@ -271,7 +271,7 @@ func (c *Client) CreateOrGetMapping(ctx context.Context) (external netaddr.IPPor
 			return m.external, nil
 		}
 		// The mapping might still be valid, so just try to renew it.
-		prevPort = m.external.Port
+		prevPort = m.external.Port()
 	}
 
 	// If we just did a Probe (e.g. via netchecker) but didn't
@@ -279,7 +279,7 @@ func (c *Client) CreateOrGetMapping(ctx context.Context) (external netaddr.IPPor
 	// again. Cuts down latency for most clients.
 	haveRecentPMP := c.sawPMPRecentlyLocked()
 	if haveRecentPMP {
-		m.external.IP = c.pmpPubIP
+		m.external = m.external.WithIP(c.pmpPubIP)
 	}
 	if c.lastProbe.After(now.Add(-5*time.Second)) && !haveRecentPMP {
 		c.mu.Unlock()
@@ -297,11 +297,11 @@ func (c *Client) CreateOrGetMapping(ctx context.Context) (external netaddr.IPPor
 	uc.SetReadDeadline(time.Now().Add(portMapServiceTimeout))
 	defer closeCloserOnContextDone(ctx, uc)()
 
-	pmpAddr := netaddr.IPPort{IP: gw, Port: pmpPort}
+	pmpAddr := netaddr.IPPortFrom(gw, pmpPort)
 	pmpAddru := pmpAddr.UDPAddr()
 
 	// Ask for our external address if needed.
-	if m.external.IP.IsZero() {
+	if m.external.IP().IsZero() {
 		if _, err := uc.WriteTo(pmpReqExternalAddrPacket, pmpAddru); err != nil {
 			return netaddr.IPPort{}, err
 		}
@@ -337,10 +337,10 @@ func (c *Client) CreateOrGetMapping(ctx context.Context) (external netaddr.IPPor
 				return netaddr.IPPort{}, NoMappingError{fmt.Errorf("PMP response Op=0x%x,Res=0x%x", pres.OpCode, pres.ResultCode)}
 			}
 			if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr {
-				m.external.IP = pres.PublicAddr
+				m.external = m.external.WithIP(pres.PublicAddr)
 			}
 			if pres.OpCode == pmpOpReply|pmpOpMapUDP {
-				m.external.Port = pres.ExternalPort
+				m.external = m.external.WithPort(pres.ExternalPort)
 				d := time.Duration(pres.MappingValidSeconds) * time.Second
 				d /= 2 // renew in half the time
 				m.useUntil = time.Now().Add(d)
@@ -468,9 +468,9 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
 	defer cancel()
 	defer closeCloserOnContextDone(ctx, uc)()
 
-	pcpAddr := netaddr.IPPort{IP: gw, Port: pcpPort}.UDPAddr()
-	pmpAddr := netaddr.IPPort{IP: gw, Port: pmpPort}.UDPAddr()
-	upnpAddr := netaddr.IPPort{IP: gw, Port: upnpPort}.UDPAddr()
+	pcpAddr := netaddr.IPPortFrom(gw, pcpPort).UDPAddr()
+	pmpAddr := netaddr.IPPortFrom(gw, pmpPort).UDPAddr()
+	upnpAddr := netaddr.IPPortFrom(gw, upnpPort).UDPAddr()
 
 	// Don't send probes to services that we recently learned (for
 	// the same gw/myIP) are available. See

+ 5 - 5
net/tsaddr/tsaddr.go

@@ -92,7 +92,7 @@ func TailscaleEphemeral6Range() netaddr.IPPrefix {
 // Currently used to work around a Windows limitation when programming
 // IPv6 routes in corner cases.
 func Tailscale4To6Placeholder() netaddr.IP {
-	return Tailscale4To6Range().IP
+	return Tailscale4To6Range().IP()
 }
 
 // Tailscale4To6 returns a Tailscale IPv6 address that maps 1:1 to the
@@ -102,7 +102,7 @@ func Tailscale4To6(ipv4 netaddr.IP) netaddr.IP {
 	if !ipv4.Is4() || !IsTailscaleIP(ipv4) {
 		return netaddr.IP{}
 	}
-	ret := Tailscale4To6Range().IP.As16()
+	ret := Tailscale4To6Range().IP().As16()
 	v4 := ipv4.As4()
 	copy(ret[13:], v4[1:])
 	return netaddr.IPFrom16(ret)
@@ -172,16 +172,16 @@ func NewContainsIPFunc(addrs []netaddr.IPPrefix) func(ip netaddr.IP) bool {
 	// Fast paths for 1 and 2 IPs:
 	if len(addrs) == 1 {
 		a := addrs[0]
-		return func(ip netaddr.IP) bool { return ip == a.IP }
+		return func(ip netaddr.IP) bool { return ip == a.IP() }
 	}
 	if len(addrs) == 2 {
 		a, b := addrs[0], addrs[1]
-		return func(ip netaddr.IP) bool { return ip == a.IP || ip == b.IP }
+		return func(ip netaddr.IP) bool { return ip == a.IP() || ip == b.IP() }
 	}
 	// General case:
 	m := map[netaddr.IP]bool{}
 	for _, a := range addrs {
-		m[a.IP] = true
+		m[a.IP()] = true
 	}
 	return func(ip netaddr.IP) bool { return m[ip] }
 }

+ 5 - 5
net/tstun/wrap.go

@@ -352,7 +352,7 @@ func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
 	p.Decode(buf[offset : offset+n])
 
 	if m, ok := t.destIPActivity.Load().(map[netaddr.IP]func()); ok {
-		if fn := m[p.Dst.IP]; fn != nil {
+		if fn := m[p.Dst.IP()]; fn != nil {
 			fn()
 		}
 	}
@@ -412,7 +412,7 @@ func (t *Wrapper) filterIn(buf []byte) filter.Response {
 		p.IPProto == ipproto.TCP &&
 		p.TCPFlags&packet.TCPSyn != 0 &&
 		t.PeerAPIPort != nil {
-		if port, ok := t.PeerAPIPort(p.Dst.IP); ok && port == p.Dst.Port {
+		if port, ok := t.PeerAPIPort(p.Dst.IP()); ok && port == p.Dst.Port() {
 			outcome = filter.Accept
 		}
 	}
@@ -425,8 +425,8 @@ func (t *Wrapper) filterIn(buf []byte) filter.Response {
 		// can show them a rejection history with reasons.
 		if p.IPVersion == 4 && p.IPProto == ipproto.TCP && p.TCPFlags&packet.TCPSyn != 0 && !t.disableTSMPRejected {
 			rj := packet.TailscaleRejectedHeader{
-				IPSrc:  p.Dst.IP,
-				IPDst:  p.Src.IP,
+				IPSrc:  p.Dst.IP(),
+				IPDst:  p.Src.IP(),
 				Src:    p.Src,
 				Dst:    p.Dst,
 				Proto:  p.IPProto,
@@ -536,7 +536,7 @@ func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingReque
 		Data: req.Data,
 	}
 	if t.PeerAPIPort != nil {
-		pong.PeerAPIPort, _ = t.PeerAPIPort(pp.Dst.IP)
+		pong.PeerAPIPort, _ = t.PeerAPIPort(pp.Dst.IP())
 	}
 	switch pp.IPVersion {
 	case 4:

+ 1 - 1
net/tstun/wrap_test.go

@@ -82,7 +82,7 @@ func nets(nets ...string) (ret []netaddr.IPPrefix) {
 			if ip.Is6() {
 				bits = 128
 			}
-			ret = append(ret, netaddr.IPPrefix{IP: ip, Bits: bits})
+			ret = append(ret, netaddr.IPPrefixFrom(ip, bits))
 		} else {
 			pfx, err := netaddr.ParseIPPrefix(s)
 			if err != nil {

+ 1 - 1
tsnet/tsnet.go

@@ -70,7 +70,7 @@ func (s *Server) WhoIs(addr string) (w *apitype.WhoIsResponse, ok bool) {
 		if err != nil {
 			return nil, false
 		}
-		ipp.IP = ip
+		ipp = ipp.WithIP(ip)
 	}
 	n, up, ok := s.lb.WhoIs(ipp)
 	if !ok {

+ 1 - 1
tstest/integration/testcontrol/testcontrol.go

@@ -650,7 +650,7 @@ func keepClientEndpoint(ep string) bool {
 		// the incoming JSON response.
 		return false
 	}
-	ip := ipp.IP
+	ip := ipp.IP()
 	if ip.Zone() != "" {
 		return false
 	}

+ 1 - 1
tstest/natlab/firewall.go

@@ -52,7 +52,7 @@ func (s FirewallType) key(src, dst netaddr.IPPort) fwKey {
 	switch s {
 	case EndpointIndependentFirewall:
 	case AddressDependentFirewall:
-		k.dst.IP = dst.IP
+		k.dst = k.dst.WithIP(dst.IP())
 	case AddressAndPortDependentFirewall:
 		k.dst = dst
 	default:

+ 3 - 6
tstest/natlab/nat.go

@@ -62,7 +62,7 @@ func (t NATType) key(src, dst netaddr.IPPort) natKey {
 	switch t {
 	case EndpointIndependentNAT:
 	case AddressDependentNAT:
-		k.dst.IP = dst.IP
+		k.dst = k.dst.WithIP(dst.IP())
 	case AddressAndPortDependentNAT:
 		k.dst = dst
 	default:
@@ -171,7 +171,7 @@ func (n *SNAT44) HandleIn(p *Packet, iif *Interface) *Packet {
 func (n *SNAT44) HandleForward(p *Packet, iif, oif *Interface) *Packet {
 	switch {
 	case oif == n.ExternalInterface:
-		if p.Src.IP == oif.V4() {
+		if p.Src.IP() == oif.V4() {
 			// Packet already NATed and is just retraversing Forward,
 			// don't touch it again.
 			return p
@@ -237,10 +237,7 @@ func (n *SNAT44) allocateMappedPort() (net.PacketConn, netaddr.IPPort) {
 	if err != nil {
 		panic(fmt.Sprintf("ran out of NAT ports: %v", err))
 	}
-	addr := netaddr.IPPort{
-		IP:   ip,
-		Port: uint16(pc.LocalAddr().(*net.UDPAddr).Port),
-	}
+	addr := netaddr.IPPortFrom(ip, uint16(pc.LocalAddr().(*net.UDPAddr).Port))
 	return pc, addr
 }
 

+ 28 - 28
tstest/natlab/natlab.go

@@ -138,7 +138,7 @@ func (n *Network) allocIPv4(iface *Interface) netaddr.IP {
 		return netaddr.IP{}
 	}
 	if n.lastV4.IsZero() {
-		n.lastV4 = n.Prefix4.IP
+		n.lastV4 = n.Prefix4.IP()
 	}
 	a := n.lastV4.As16()
 	addOne(&a, 15)
@@ -157,7 +157,7 @@ func (n *Network) allocIPv6(iface *Interface) netaddr.IP {
 		return netaddr.IP{}
 	}
 	if n.lastV6.IsZero() {
-		n.lastV6 = n.Prefix6.IP
+		n.lastV6 = n.Prefix6.IP()
 	}
 	a := n.lastV6.As16()
 	addOne(&a, 15)
@@ -183,15 +183,15 @@ func (n *Network) write(p *Packet) (num int, err error) {
 
 	n.mu.Lock()
 	defer n.mu.Unlock()
-	iface, ok := n.machine[p.Dst.IP]
+	iface, ok := n.machine[p.Dst.IP()]
 	if !ok {
 		// If the destination is within the network's authoritative
 		// range, no route to host.
-		if p.Dst.IP.Is4() && n.Prefix4.Contains(p.Dst.IP) {
+		if p.Dst.IP().Is4() && n.Prefix4.Contains(p.Dst.IP()) {
 			p.Trace("no route to %v", p.Dst.IP)
 			return len(p.Payload), nil
 		}
-		if p.Dst.IP.Is6() && n.Prefix6.Contains(p.Dst.IP) {
+		if p.Dst.IP().Is6() && n.Prefix6.Contains(p.Dst.IP()) {
 			p.Trace("no route to %v", p.Dst.IP)
 			return len(p.Payload), nil
 		}
@@ -363,7 +363,7 @@ func (m *Machine) isLocalIP(ip netaddr.IP) bool {
 func (m *Machine) deliverIncomingPacket(p *Packet, iface *Interface) {
 	p.setLocator("mach=%s if=%s", m.Name, iface.name)
 
-	if m.isLocalIP(p.Dst.IP) {
+	if m.isLocalIP(p.Dst.IP()) {
 		m.deliverLocalPacket(p, iface)
 	} else {
 		m.forwardPacket(p, iface)
@@ -391,13 +391,13 @@ func (m *Machine) deliverLocalPacket(p *Packet, iface *Interface) {
 	defer m.mu.Unlock()
 
 	conns := m.conns4
-	if p.Dst.IP.Is6() {
+	if p.Dst.IP().Is6() {
 		conns = m.conns6
 	}
 	possibleDsts := []netaddr.IPPort{
 		p.Dst,
-		netaddr.IPPort{IP: v6unspec, Port: p.Dst.Port},
-		netaddr.IPPort{IP: v4unspec, Port: p.Dst.Port},
+		netaddr.IPPortFrom(v6unspec, p.Dst.Port()),
+		netaddr.IPPortFrom(v4unspec, p.Dst.Port()),
 	}
 	for _, dest := range possibleDsts {
 		c, ok := conns[dest]
@@ -417,7 +417,7 @@ func (m *Machine) deliverLocalPacket(p *Packet, iface *Interface) {
 }
 
 func (m *Machine) forwardPacket(p *Packet, iif *Interface) {
-	oif, err := m.interfaceForIP(p.Dst.IP)
+	oif, err := m.interfaceForIP(p.Dst.IP())
 	if err != nil {
 		p.Trace("%v", err)
 		return
@@ -501,7 +501,7 @@ func (m *Machine) Attach(interfaceName string, n *Network) *Interface {
 		}
 	}
 	sort.Slice(m.routes, func(i, j int) bool {
-		return m.routes[i].prefix.Bits > m.routes[j].prefix.Bits
+		return m.routes[i].prefix.Bits() > m.routes[j].prefix.Bits()
 	})
 
 	return f
@@ -515,33 +515,33 @@ var (
 func (m *Machine) writePacket(p *Packet) (n int, err error) {
 	p.setLocator("mach=%s", m.Name)
 
-	iface, err := m.interfaceForIP(p.Dst.IP)
+	iface, err := m.interfaceForIP(p.Dst.IP())
 	if err != nil {
 		p.Trace("%v", err)
 		return 0, err
 	}
-	origSrcIP := p.Src.IP
+	origSrcIP := p.Src.IP()
 	switch {
-	case p.Src.IP == v4unspec:
+	case p.Src.IP() == v4unspec:
 		p.Trace("assigning srcIP=%s", iface.V4())
-		p.Src.IP = iface.V4()
-	case p.Src.IP == v6unspec:
+		p.Src = p.Src.WithIP(iface.V4())
+	case p.Src.IP() == v6unspec:
 		// v6unspec in Go means "any src, but match address families"
-		if p.Dst.IP.Is6() {
+		if p.Dst.IP().Is6() {
 			p.Trace("assigning srcIP=%s", iface.V6())
-			p.Src.IP = iface.V6()
-		} else if p.Dst.IP.Is4() {
+			p.Src = p.Src.WithIP(iface.V6())
+		} else if p.Dst.IP().Is4() {
 			p.Trace("assigning srcIP=%s", iface.V4())
-			p.Src.IP = iface.V4()
+			p.Src = p.Src.WithIP(iface.V4())
 		}
 	default:
-		if !iface.Contains(p.Src.IP) {
-			err := fmt.Errorf("can't send to %v with src %v on interface %v", p.Dst.IP, p.Src.IP, iface)
+		if !iface.Contains(p.Src.IP()) {
+			err := fmt.Errorf("can't send to %v with src %v on interface %v", p.Dst.IP(), p.Src.IP(), iface)
 			p.Trace("%v", err)
 			return 0, err
 		}
 	}
-	if p.Src.IP.IsZero() {
+	if p.Src.IP().IsZero() {
 		err := fmt.Errorf("no matching address for address family for %v", origSrcIP)
 		p.Trace("%v", err)
 		return 0, err
@@ -602,12 +602,12 @@ func (m *Machine) pickEphemPort() (port uint16, err error) {
 
 func (m *Machine) portInUseLocked(port uint16) bool {
 	for ipp := range m.conns4 {
-		if ipp.Port == port {
+		if ipp.Port() == port {
 			return true
 		}
 	}
 	for ipp := range m.conns6 {
-		if ipp.Port == port {
+		if ipp.Port() == port {
 			return true
 		}
 	}
@@ -617,7 +617,7 @@ func (m *Machine) portInUseLocked(port uint16) bool {
 func (m *Machine) registerConn4(c *conn) error {
 	m.mu.Lock()
 	defer m.mu.Unlock()
-	if c.ipp.IP.Is6() && c.ipp.IP != v6unspec {
+	if c.ipp.IP().Is6() && c.ipp.IP() != v6unspec {
 		return fmt.Errorf("registerConn4 got IPv6 %s", c.ipp)
 	}
 	return registerConn(&m.conns4, c)
@@ -632,7 +632,7 @@ func (m *Machine) unregisterConn4(c *conn) {
 func (m *Machine) registerConn6(c *conn) error {
 	m.mu.Lock()
 	defer m.mu.Unlock()
-	if c.ipp.IP.Is4() {
+	if c.ipp.IP().Is4() {
 		return fmt.Errorf("registerConn6 got IPv4 %s", c.ipp)
 	}
 	return registerConn(&m.conns6, c)
@@ -707,7 +707,7 @@ func (m *Machine) ListenPacket(ctx context.Context, network, address string) (ne
 			return nil, nil
 		}
 	}
-	ipp := netaddr.IPPort{IP: ip, Port: port}
+	ipp := netaddr.IPPortFrom(ip, port)
 
 	c := &conn{
 		m:   m,

+ 13 - 13
tstest/natlab/natlab_test.go

@@ -49,8 +49,8 @@ func TestSendPacket(t *testing.T) {
 	ifFoo := foo.Attach("eth0", internet)
 	ifBar := bar.Attach("enp0s1", internet)
 
-	fooAddr := netaddr.IPPort{IP: ifFoo.V4(), Port: 123}
-	barAddr := netaddr.IPPort{IP: ifBar.V4(), Port: 456}
+	fooAddr := netaddr.IPPortFrom(ifFoo.V4(), 123)
+	barAddr := netaddr.IPPortFrom(ifBar.V4(), 456)
 
 	ctx := context.Background()
 	fooPC, err := foo.ListenPacket(ctx, "udp4", fooAddr.String())
@@ -111,10 +111,10 @@ func TestMultiNetwork(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	clientAddr := netaddr.IPPort{IP: ifClient.V4(), Port: 123}
-	natLANAddr := netaddr.IPPort{IP: ifNATLAN.V4(), Port: 456}
-	natWANAddr := netaddr.IPPort{IP: ifNATWAN.V4(), Port: 456}
-	serverAddr := netaddr.IPPort{IP: ifServer.V4(), Port: 789}
+	clientAddr := netaddr.IPPortFrom(ifClient.V4(), 123)
+	natLANAddr := netaddr.IPPortFrom(ifNATLAN.V4(), 456)
+	natWANAddr := netaddr.IPPortFrom(ifNATWAN.V4(), 456)
+	serverAddr := netaddr.IPPortFrom(ifServer.V4(), 789)
 
 	const msg1, msg2 = "hello", "world"
 	if _, err := natPC.WriteTo([]byte(msg1), clientAddr.UDPAddr()); err != nil {
@@ -154,8 +154,8 @@ type trivialNAT struct {
 }
 
 func (n *trivialNAT) HandleIn(p *Packet, iface *Interface) *Packet {
-	if iface == n.wanIf && p.Dst.IP == n.wanIf.V4() {
-		p.Dst.IP = n.clientIP
+	if iface == n.wanIf && p.Dst.IP() == n.wanIf.V4() {
+		p.Dst = p.Dst.WithIP(n.clientIP)
 	}
 	return p
 }
@@ -167,13 +167,13 @@ func (n trivialNAT) HandleOut(p *Packet, iface *Interface) *Packet {
 func (n *trivialNAT) HandleForward(p *Packet, iif, oif *Interface) *Packet {
 	// Outbound from LAN -> apply NAT, continue
 	if iif == n.lanIf && oif == n.wanIf {
-		if p.Src.IP == n.clientIP {
-			p.Src.IP = n.wanIf.V4()
+		if p.Src.IP() == n.clientIP {
+			p.Src = p.Src.WithIP(n.wanIf.V4())
 		}
 		return p
 	}
 	// Return traffic to LAN, allow if right dst.
-	if iif == n.wanIf && oif == n.lanIf && p.Dst.IP == n.clientIP {
+	if iif == n.wanIf && oif == n.lanIf && p.Dst.IP() == n.clientIP {
 		return p
 	}
 	// Else drop.
@@ -216,7 +216,7 @@ func TestPacketHandler(t *testing.T) {
 	}
 
 	const msg = "some message"
-	serverAddr := netaddr.IPPort{IP: ifServer.V4(), Port: 456}
+	serverAddr := netaddr.IPPortFrom(ifServer.V4(), 456)
 	if _, err := clientPC.WriteTo([]byte(msg), serverAddr.UDPAddr()); err != nil {
 		t.Fatal(err)
 	}
@@ -230,7 +230,7 @@ func TestPacketHandler(t *testing.T) {
 	if string(buf) != msg {
 		t.Errorf("read %q; want %q", buf, msg)
 	}
-	mappedAddr := netaddr.IPPort{IP: ifNATWAN.V4(), Port: 123}
+	mappedAddr := netaddr.IPPortFrom(ifNATWAN.V4(), 123)
 	if addr.String() != mappedAddr.String() {
 		t.Errorf("addr = %q; want %q", addr, mappedAddr)
 	}

+ 2 - 2
types/netmap/netmap_test.go

@@ -250,7 +250,7 @@ func TestConciseDiffFrom(t *testing.T) {
 						DERP:       "127.3.3.40:2",
 						Endpoints:  []string{"192.168.0.100:41641", "1.1.1.1:41641"},
 						DiscoKey:   testDiscoKey("f00f00f00f"),
-						AllowedIPs: []netaddr.IPPrefix{{IP: netaddr.IPv4(100, 102, 103, 104), Bits: 32}},
+						AllowedIPs: []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
 					},
 				},
 			},
@@ -263,7 +263,7 @@ func TestConciseDiffFrom(t *testing.T) {
 						DERP:       "127.3.3.40:2",
 						Endpoints:  []string{"192.168.0.100:41641", "1.1.1.1:41641"},
 						DiscoKey:   testDiscoKey("ba4ba4ba4b"),
-						AllowedIPs: []netaddr.IPPrefix{{IP: netaddr.IPv4(100, 102, 103, 104), Bits: 32}},
+						AllowedIPs: []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
 					},
 				},
 			},

+ 1 - 1
wf/firewall.go

@@ -218,7 +218,7 @@ func (f *Firewall) UpdatePermittedRoutes(newRoutes []netaddr.IPPrefix) error {
 			},
 		}
 		var p protocol
-		if r.IP.Is4() {
+		if r.IP().Is4() {
 			p = protocolV4
 		} else {
 			p = protocolV6

+ 1 - 1
wgengine/bench/bench.go

@@ -87,7 +87,7 @@ func main() {
 	}
 
 	logf("initialized ok.")
-	traf.Start(Addr1.IP, Addr2.IP, PayloadSize+ICMPMinSize, 0)
+	traf.Start(Addr1.IP(), Addr2.IP(), PayloadSize+ICMPMinSize, 0)
 
 	var cur, prev Snapshot
 	var pps int64

+ 1 - 1
wgengine/bench/bench_test.go

@@ -78,7 +78,7 @@ func runOnce(b *testing.B, setup SetupFunc, payload int) {
 	logf("initialized. (n=%v)", b.N)
 	b.SetBytes(int64(payload))
 
-	traf.Start(Addr1.IP, Addr2.IP, payload, int64(b.N))
+	traf.Start(Addr1.IP(), Addr2.IP(), payload, int64(b.N))
 
 	var cur, prev Snapshot
 	var pps int64

+ 12 - 14
wgengine/filter/filter.go

@@ -98,8 +98,8 @@ const (
 // everything. Use in tests only, as it permits some kinds of spoofing
 // attacks to reach the OS network stack.
 func NewAllowAllForTest(logf logger.Logf) *Filter {
-	any4 := netaddr.IPPrefix{IP: netaddr.IPv4(0, 0, 0, 0), Bits: 0}
-	any6 := netaddr.IPPrefix{IP: netaddr.IPFrom16([16]byte{}), Bits: 0}
+	any4 := netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0)
+	any6 := netaddr.IPPrefixFrom(netaddr.IPFrom16([16]byte{}), 0)
 	ms := []Match{
 		{
 			Srcs: []netaddr.IPPrefix{any4},
@@ -185,12 +185,12 @@ func matchesFamily(ms matches, keep func(netaddr.IP) bool) matches {
 		var retm Match
 		retm.IPProto = m.IPProto
 		for _, src := range m.Srcs {
-			if keep(src.IP) {
+			if keep(src.IP()) {
 				retm.Srcs = append(retm.Srcs, src)
 			}
 		}
 		for _, dst := range m.Dsts {
-			if keep(dst.Net.IP) {
+			if keep(dst.Net.IP()) {
 				retm.Dsts = append(retm.Dsts, dst)
 			}
 		}
@@ -266,12 +266,10 @@ func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response {
 	default:
 		panic("unreachable")
 	}
-	pkt.Src.IP = srcIP
-	pkt.Dst.IP = dstIP
+	pkt.Src = netaddr.IPPortFrom(srcIP, 0)
+	pkt.Dst = netaddr.IPPortFrom(dstIP, dstPort)
 	pkt.IPProto = ipproto.TCP
 	pkt.TCPFlags = packet.TCPSyn
-	pkt.Src.Port = 0
-	pkt.Dst.Port = dstPort
 
 	return f.RunIn(pkt, 0)
 }
@@ -321,7 +319,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
 	// A compromised peer could try to send us packets for
 	// destinations we didn't explicitly advertise. This check is to
 	// prevent that.
-	if !f.local.Contains(q.Dst.IP) {
+	if !f.local.Contains(q.Dst.IP()) {
 		return Drop, "destination not allowed"
 	}
 
@@ -378,7 +376,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
 	// A compromised peer could try to send us packets for
 	// destinations we didn't explicitly advertise. This check is to
 	// prevent that.
-	if !f.local.Contains(q.Dst.IP) {
+	if !f.local.Contains(q.Dst.IP()) {
 		return Drop, "destination not allowed"
 	}
 
@@ -480,11 +478,11 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
 		return Drop
 	}
 
-	if q.Dst.IP.IsMulticast() {
+	if q.Dst.IP().IsMulticast() {
 		f.logRateLimit(rf, q, dir, Drop, "multicast")
 		return Drop
 	}
-	if q.Dst.IP.IsLinkLocalUnicast() && q.Dst.IP != gcpDNSAddr {
+	if q.Dst.IP().IsLinkLocalUnicast() && q.Dst.IP() != gcpDNSAddr {
 		f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
 		return Drop
 	}
@@ -506,7 +504,7 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
 
 // loggingAllowed reports whether p can appear in logs at all.
 func (f *Filter) loggingAllowed(p *packet.Parsed) bool {
-	return f.logIPs.Contains(p.Src.IP) && f.logIPs.Contains(p.Dst.IP)
+	return f.logIPs.Contains(p.Src.IP()) && f.logIPs.Contains(p.Dst.IP())
 }
 
 // omitDropLogging reports whether packet p, which has already been
@@ -518,5 +516,5 @@ func omitDropLogging(p *packet.Parsed, dir direction) bool {
 		return false
 	}
 
-	return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == ipproto.IGMP
+	return p.Dst.IP().IsMulticast() || (p.Dst.IP().IsLinkLocalUnicast() && p.Dst.IP() != gcpDNSAddr) || p.IPProto == ipproto.IGMP
 }

+ 12 - 12
wgengine/filter/filter_test.go

@@ -120,9 +120,9 @@ func TestFilter(t *testing.T) {
 		if test.p.IPProto == ipproto.TCP {
 			var got Response
 			if test.p.IPVersion == 4 {
-				got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
+				got = acl.CheckTCP(test.p.Src.IP(), test.p.Dst.IP(), test.p.Dst.Port())
 			} else {
-				got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
+				got = acl.CheckTCP(test.p.Src.IP(), test.p.Dst.IP(), test.p.Dst.Port())
 			}
 			if test.want != got {
 				t.Errorf("#%d CheckTCP got=%v want=%v packet:%v", i, got, test.want, test.p)
@@ -254,7 +254,9 @@ func TestParseIPSet(t *testing.T) {
 			}
 			t.Errorf("parseIPSet(%q, %v) error: %v; want error %q", tt.host, tt.bits, err, tt.wantErr)
 		}
-		if diff := cmp.Diff(got, tt.want, cmp.Comparer(func(a, b netaddr.IP) bool { return a == b })); diff != "" {
+		compareIP := cmp.Comparer(func(a, b netaddr.IP) bool { return a == b })
+		compareIPPrefix := cmp.Comparer(func(a, b netaddr.IPPrefix) bool { return a == b })
+		if diff := cmp.Diff(got, tt.want, compareIP, compareIPPrefix); diff != "" {
 			t.Errorf("parseIPSet(%q, %v) = %s; want %s", tt.host, tt.bits, got, tt.want)
 			continue
 		}
@@ -425,10 +427,10 @@ func TestLoggingPrivacy(t *testing.T) {
 	f.logIPs = logB.IPSet()
 
 	var (
-		ts4       = netaddr.IPPort{IP: tsaddr.CGNATRange().IP.Next(), Port: 1234}
-		internet4 = netaddr.IPPort{IP: netaddr.MustParseIP("8.8.8.8"), Port: 1234}
-		ts6       = netaddr.IPPort{IP: tsaddr.TailscaleULARange().IP.Next(), Port: 1234}
-		internet6 = netaddr.IPPort{IP: netaddr.MustParseIP("2001::1"), Port: 1234}
+		ts4       = netaddr.IPPortFrom(tsaddr.CGNATRange().IP().Next(), 1234)
+		internet4 = netaddr.IPPortFrom(netaddr.MustParseIP("8.8.8.8"), 1234)
+		ts6       = netaddr.IPPortFrom(tsaddr.TailscaleULARange().IP().Next(), 1234)
+		internet6 = netaddr.IPPortFrom(netaddr.MustParseIP("2001::1"), 1234)
 	)
 
 	tests := []struct {
@@ -545,10 +547,8 @@ func parsed(proto ipproto.Proto, src, dst string, sport, dport uint16) packet.Pa
 	var ret packet.Parsed
 	ret.Decode(dummyPacket)
 	ret.IPProto = proto
-	ret.Src.IP = sip
-	ret.Src.Port = sport
-	ret.Dst.IP = dip
-	ret.Dst.Port = dport
+	ret.Src = netaddr.IPPortFrom(sip, sport)
+	ret.Dst = netaddr.IPPortFrom(dip, dport)
 	ret.TCPFlags = packet.TCPSyn
 
 	if sip.Is4() {
@@ -674,7 +674,7 @@ func nets(nets ...string) (ret []netaddr.IPPrefix) {
 			if ip.Is6() {
 				bits = 128
 			}
-			ret = append(ret, netaddr.IPPrefix{IP: ip, Bits: bits})
+			ret = append(ret, netaddr.IPPrefixFrom(ip, bits))
 		} else {
 			pfx, err := netaddr.ParseIPPrefix(s)
 			if err != nil {

+ 5 - 5
wgengine/filter/match.go

@@ -85,14 +85,14 @@ func (ms matches) match(q *packet.Parsed) bool {
 		if !protoInList(q.IPProto, m.IPProto) {
 			continue
 		}
-		if !ipInList(q.Src.IP, m.Srcs) {
+		if !ipInList(q.Src.IP(), m.Srcs) {
 			continue
 		}
 		for _, dst := range m.Dsts {
-			if !dst.Net.Contains(q.Dst.IP) {
+			if !dst.Net.Contains(q.Dst.IP()) {
 				continue
 			}
-			if !dst.Ports.contains(q.Dst.Port) {
+			if !dst.Ports.contains(q.Dst.Port()) {
 				continue
 			}
 			return true
@@ -103,11 +103,11 @@ func (ms matches) match(q *packet.Parsed) bool {
 
 func (ms matches) matchIPsOnly(q *packet.Parsed) bool {
 	for _, m := range ms {
-		if !ipInList(q.Src.IP, m.Srcs) {
+		if !ipInList(q.Src.IP(), m.Srcs) {
 			continue
 		}
 		for _, dst := range m.Dsts {
-			if dst.Net.Contains(q.Dst.IP) {
+			if dst.Net.Contains(q.Dst.IP()) {
 				return true
 			}
 		}

+ 4 - 4
wgengine/filter/tailcfg.go

@@ -99,8 +99,8 @@ func parseIPSet(arg string, bits *int) ([]netaddr.IPPrefix, error) {
 	if arg == "*" {
 		// User explicitly requested wildcard.
 		return []netaddr.IPPrefix{
-			{IP: zeroIP4, Bits: 0},
-			{IP: zeroIP6, Bits: 0},
+			netaddr.IPPrefixFrom(zeroIP4, 0),
+			netaddr.IPPrefixFrom(zeroIP6, 0),
 		}, nil
 	}
 	if strings.Contains(arg, "/") {
@@ -124,7 +124,7 @@ func parseIPSet(arg string, bits *int) ([]netaddr.IPPrefix, error) {
 		if err != nil {
 			return nil, err
 		}
-		r := netaddr.IPRange{From: ip1, To: ip2}
+		r := netaddr.IPRangeFrom(ip1, ip2)
 		if !r.Valid() {
 			return nil, fmt.Errorf("invalid IP range %q", arg)
 		}
@@ -141,5 +141,5 @@ func parseIPSet(arg string, bits *int) ([]netaddr.IPPrefix, error) {
 		}
 		bits8 = uint8(*bits)
 	}
-	return []netaddr.IPPrefix{{IP: ip, Bits: bits8}}, nil
+	return []netaddr.IPPrefix{netaddr.IPPrefixFrom(ip, bits8)}, nil
 }

+ 7 - 7
wgengine/magicsock/legacy.go

@@ -62,7 +62,7 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs wgcfg.IPPortSet,
 
 	// Add entries to c.addrsByUDP.
 	for _, ipp := range a.ipPorts {
-		if ipp.IP == derpMagicIPAddr {
+		if ipp.IP() == derpMagicIPAddr {
 			continue
 		}
 		c.addrsByUDP[ipp] = a
@@ -70,7 +70,7 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs wgcfg.IPPortSet,
 
 	// Remove previous c.addrsByUDP entries that are no longer in the new set.
 	for _, ipp := range oldIPP {
-		if ipp.IP != derpMagicIPAddr && c.addrsByUDP[ipp] != a {
+		if ipp.IP() != derpMagicIPAddr && c.addrsByUDP[ipp] != a {
 			delete(c.addrsByUDP, ipp)
 		}
 	}
@@ -388,8 +388,8 @@ type addrSet struct {
 // derpID returns this addrSet's home DERP node, or 0 if none is found.
 func (as *addrSet) derpID() int {
 	for _, ua := range as.ipPorts {
-		if ua.IP == derpMagicIPAddr {
-			return int(ua.Port)
+		if ua.IP() == derpMagicIPAddr {
+			return int(ua.Port())
 		}
 	}
 	return 0
@@ -428,7 +428,7 @@ func (a *addrSet) DstToString() string {
 	return a.rawdst
 }
 func (a *addrSet) DstIP() net.IP {
-	return a.dst().IP.IPAddr().IP // TODO: add netaddr accessor to cut an alloc here?
+	return a.dst().IP().IPAddr().IP // TODO: add netaddr accessor to cut an alloc here?
 }
 func (a *addrSet) SrcIP() net.IP       { return nil }
 func (a *addrSet) SrcToString() string { return "" }
@@ -437,7 +437,7 @@ func (a *addrSet) ClearSrc()           {}
 // updateDst records receipt of a packet from new. This is used to
 // potentially update the transmit address used for this addrSet.
 func (a *addrSet) updateDst(new netaddr.IPPort) error {
-	if new.IP == derpMagicIPAddr {
+	if new.IP() == derpMagicIPAddr {
 		// Never consider DERP addresses as a viable candidate for
 		// either curAddr or roamAddr. It's only ever a last resort
 		// choice, never a preferred choice.
@@ -539,7 +539,7 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) {
 
 	ps.LastWrite = as.lastSend
 	for i, ua := range as.ipPorts {
-		if ua.IP == derpMagicIPAddr {
+		if ua.IP() == derpMagicIPAddr {
 			continue
 		}
 		uaStr := ua.String()

+ 28 - 31
wgengine/magicsock/magicsock.go

@@ -832,7 +832,7 @@ func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnst
 		return
 	}
 	if len(peer.Addresses) > 0 {
-		res.NodeIP = peer.Addresses[0].IP.String()
+		res.NodeIP = peer.Addresses[0].IP().String()
 	}
 	res.NodeName = peer.Name // prefer DNS name
 	if res.NodeName == "" {
@@ -878,11 +878,11 @@ func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnst
 // c.mu must be held
 func (c *Conn) populateCLIPingResponseLocked(res *ipnstate.PingResult, latency time.Duration, ep netaddr.IPPort) {
 	res.LatencySeconds = latency.Seconds()
-	if ep.IP != derpMagicIPAddr {
+	if ep.IP() != derpMagicIPAddr {
 		res.Endpoint = ep.String()
 		return
 	}
-	regionID := int(ep.Port)
+	regionID := int(ep.Port())
 	res.DERPRegionID = regionID
 	if c.derpMap != nil {
 		if dr, ok := c.derpMap.Regions[regionID]; ok {
@@ -965,7 +965,7 @@ func (c *Conn) goDerpConnect(node int) {
 	if node == 0 {
 		return
 	}
-	go c.derpWriteChanOfAddr(netaddr.IPPort{IP: derpMagicIPAddr, Port: uint16(node)}, key.Public{})
+	go c.derpWriteChanOfAddr(netaddr.IPPortFrom(derpMagicIPAddr, uint16(node)), key.Public{})
 }
 
 // determineEndpoints returns the machine's endpoint addresses. It
@@ -1037,7 +1037,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, erro
 			ips = loopback
 		}
 		for _, ip := range ips {
-			addAddr(netaddr.IPPort{IP: ip, Port: uint16(localAddr.Port)}, tailcfg.EndpointLocal)
+			addAddr(netaddr.IPPortFrom(ip, uint16(localAddr.Port)), tailcfg.EndpointLocal)
 		}
 	} else {
 		// Our local endpoint is bound to a particular address.
@@ -1169,7 +1169,7 @@ func (c *Conn) sendUDPStd(addr *net.UDPAddr, b []byte) (sent bool, err error) {
 // IPv6 address when the local machine doesn't have IPv6 support
 // returns (false, nil); it's not an error, but nothing was sent.
 func (c *Conn) sendAddr(addr netaddr.IPPort, pubKey key.Public, b []byte) (sent bool, err error) {
-	if addr.IP != derpMagicIPAddr {
+	if addr.IP() != derpMagicIPAddr {
 		return c.sendUDP(addr, b)
 	}
 
@@ -1211,10 +1211,10 @@ const bufferedDerpWritesBeforeDrop = 32
 // If peer is non-zero, it can be used to find an active reverse
 // path, without using addr.
 func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.Public) chan<- derpWriteRequest {
-	if addr.IP != derpMagicIPAddr {
+	if addr.IP() != derpMagicIPAddr {
 		return nil
 	}
-	regionID := int(addr.Port)
+	regionID := int(addr.Port())
 
 	if c.networkDown() {
 		return nil
@@ -1402,7 +1402,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
 	}
 
 	didCopy := make(chan struct{}, 1)
-	regionID := int(derpFakeAddr.Port)
+	regionID := int(derpFakeAddr.Port())
 	res := derpReadResult{regionID: regionID}
 	var pkt derp.ReceivedPacket
 	res.copyBuf = func(dst []byte) int {
@@ -1676,7 +1676,7 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep con
 		return 0, nil
 	}
 
-	ipp := netaddr.IPPort{IP: derpMagicIPAddr, Port: uint16(regionID)}
+	ipp := netaddr.IPPortFrom(derpMagicIPAddr, uint16(regionID))
 	if c.handleDiscoMessage(b[:n], ipp) {
 		return 0, nil
 	}
@@ -1922,7 +1922,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) (isDiscoMsg bo
 		}
 		de.handlePongConnLocked(dm, src)
 	case *disco.CallMeMaybe:
-		if src.IP != derpMagicIPAddr {
+		if src.IP() != derpMagicIPAddr {
 			// CallMeMaybe messages should only come via DERP.
 			c.logf("[unexpected] CallMeMaybe packets should only come via DERP")
 			return
@@ -2722,7 +2722,7 @@ func (c *Conn) resetEndpointStates() {
 
 // packIPPort packs an IPPort into the form wanted by WireGuard.
 func packIPPort(ua netaddr.IPPort) []byte {
-	ip := ua.IP.Unmap()
+	ip := ua.IP().Unmap()
 	a := ip.As16()
 	ipb := a[:]
 	if ip.Is4() {
@@ -2730,8 +2730,8 @@ func packIPPort(ua netaddr.IPPort) []byte {
 	}
 	b := make([]byte, 0, len(ipb)+2)
 	b = append(b, ipb...)
-	b = append(b, byte(ua.Port))
-	b = append(b, byte(ua.Port>>8))
+	b = append(b, byte(ua.Port()))
+	b = append(b, byte(ua.Port()>>8))
 	return b
 }
 
@@ -2972,15 +2972,15 @@ func peerShort(k key.Public) string {
 }
 
 func sbPrintAddr(sb *strings.Builder, a netaddr.IPPort) {
-	is6 := a.IP.Is6()
+	is6 := a.IP().Is6()
 	if is6 {
 		sb.WriteByte('[')
 	}
-	fmt.Fprintf(sb, "%s", a.IP)
+	fmt.Fprintf(sb, "%s", a.IP())
 	if is6 {
 		sb.WriteByte(']')
 	}
-	fmt.Fprintf(sb, ":%d", a.Port)
+	fmt.Fprintf(sb, ":%d", a.Port())
 }
 
 func (c *Conn) derpRegionCodeOfAddrLocked(ipPort string) string {
@@ -3017,15 +3017,15 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
 			if !addr.IsSingleIP() {
 				continue
 			}
-			sb.AddTailscaleIP(addr.IP)
+			sb.AddTailscaleIP(addr.IP())
 			// TailAddr previously only allowed for a
 			// single Tailscale IP. For compatibility for
 			// a couple releases starting with 1.8, keep
 			// that field pulled out separately.
-			if addr.IP.Is4() {
-				tailAddr4 = addr.IP.String()
+			if addr.IP().Is4() {
+				tailAddr4 = addr.IP().String()
 			}
-			tailscaleIPs = append(tailscaleIPs, addr.IP)
+			tailscaleIPs = append(tailscaleIPs, addr.IP())
 		}
 	}
 
@@ -3084,8 +3084,8 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
 }
 
 func ippDebugString(ua netaddr.IPPort) string {
-	if ua.IP == derpMagicIPAddr {
-		return fmt.Sprintf("derp-%d", ua.Port)
+	if ua.IP() == derpMagicIPAddr {
+		return fmt.Sprintf("derp-%d", ua.Port())
 	}
 	return ua.String()
 }
@@ -3254,10 +3254,7 @@ func (de *discoEndpoint) initFakeUDPAddr() {
 	addr[0] = 0xfd
 	addr[1] = 0x00
 	binary.BigEndian.PutUint64(addr[2:], uint64(reflect.ValueOf(de).Pointer()))
-	de.fakeWGAddr = netaddr.IPPort{
-		IP:   netaddr.IPFrom16(addr),
-		Port: 12345,
-	}
+	de.fakeWGAddr = netaddr.IPPortFrom(netaddr.IPFrom16(addr), 12345)
 }
 
 // isFirstRecvActivityInAwhile notes that receive activity has occured for this
@@ -3632,7 +3629,7 @@ func (de *discoEndpoint) handlePongConnLocked(m *disco.Pong, src netaddr.IPPort)
 	de.mu.Lock()
 	defer de.mu.Unlock()
 
-	isDerp := src.IP == derpMagicIPAddr
+	isDerp := src.IP() == derpMagicIPAddr
 
 	sp, ok := de.sentPing[m.TxID]
 	if !ok {
@@ -3708,13 +3705,13 @@ func betterAddr(a, b addrLatency) bool {
 	if a.IsZero() {
 		return false
 	}
-	if a.IP.Is6() && b.IP.Is4() {
+	if a.IP().Is6() && b.IP().Is4() {
 		// Prefer IPv6 for being a bit more robust, as long as
 		// the latencies are roughly equivalent.
 		if a.latency/10*9 < b.latency {
 			return true
 		}
-	} else if a.IP.Is4() && b.IP.Is6() {
+	} else if a.IP().Is4() && b.IP().Is6() {
 		if betterAddr(b, a) {
 			return false
 		}
@@ -3754,7 +3751,7 @@ func (de *discoEndpoint) handleCallMeMaybe(m *disco.CallMeMaybe) {
 	}
 	var newEPs []netaddr.IPPort
 	for _, ep := range m.MyNumber {
-		if ep.IP.Is6() && ep.IP.IsLinkLocalUnicast() {
+		if ep.IP().Is6() && ep.IP().IsLinkLocalUnicast() {
 			// We send these out, but ignore them for now.
 			// TODO: teach the ping code to ping on all interfaces
 			// for these.

+ 6 - 6
wgengine/magicsock/magicsock_test.go

@@ -252,13 +252,13 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) {
 		nm := &netmap.NetworkMap{
 			PrivateKey: me.privateKey,
 			NodeKey:    tailcfg.NodeKey(me.privateKey.Public()),
-			Addresses:  []netaddr.IPPrefix{{IP: netaddr.IPv4(1, 0, 0, byte(myIdx+1)), Bits: 32}},
+			Addresses:  []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPv4(1, 0, 0, byte(myIdx+1)), 32)},
 		}
 		for i, peer := range ms {
 			if i == myIdx {
 				continue
 			}
-			addrs := []netaddr.IPPrefix{{IP: netaddr.IPv4(1, 0, 0, byte(i+1)), Bits: 32}}
+			addrs := []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPv4(1, 0, 0, byte(i+1)), 32)}
 			peer := &tailcfg.Node{
 				ID:         tailcfg.NodeID(i + 1),
 				Name:       fmt.Sprintf("node%d", i+1),
@@ -433,7 +433,7 @@ func TestPickDERPFallback(t *testing.T) {
 	// But move if peers are elsewhere.
 	const otherNode = 789
 	c.addrsByKey = map[key.Public]*addrSet{
-		{1}: {ipPorts: []netaddr.IPPort{{IP: derpMagicIPAddr, Port: otherNode}}},
+		{1}: {ipPorts: []netaddr.IPPort{netaddr.IPPortFrom(derpMagicIPAddr, otherNode)}},
 	}
 	if got := c.pickDERPFallback(); got != otherNode {
 		t.Errorf("didn't join peers: got %v; want %v", got, someNode)
@@ -887,8 +887,8 @@ func testTwoDevicePing(t *testing.T, d *devices) {
 	defer m2.Close()
 
 	addrs := []netaddr.IPPort{
-		{IP: d.m1IP, Port: m1.conn.LocalPort()},
-		{IP: d.m2IP, Port: m2.conn.LocalPort()},
+		netaddr.IPPortFrom(d.m1IP, m1.conn.LocalPort()),
+		netaddr.IPPortFrom(d.m2IP, m2.conn.LocalPort()),
 	}
 	cfgs := makeConfigs(t, addrs)
 
@@ -1555,7 +1555,7 @@ func TestEndpointSetsEqual(t *testing.T) {
 	s := func(ports ...uint16) (ret []tailcfg.Endpoint) {
 		for _, port := range ports {
 			ret = append(ret, tailcfg.Endpoint{
-				Addr: netaddr.IPPort{Port: port},
+				Addr: netaddr.IPPortFrom(netaddr.IP{}, port),
 			})
 		}
 		return

+ 3 - 3
wgengine/monitor/monitor_linux.go

@@ -130,11 +130,11 @@ func netaddrIP(std net.IP) netaddr.IP {
 
 func netaddrIPPrefix(std net.IP, bits uint8) netaddr.IPPrefix {
 	ip, _ := netaddr.FromStdIP(std)
-	return netaddr.IPPrefix{IP: ip, Bits: bits}
+	return netaddr.IPPrefixFrom(ip, bits)
 }
 
 func condNetAddrPrefix(ipp netaddr.IPPrefix) string {
-	if ipp.IP.IsZero() {
+	if ipp.IP().IsZero() {
 		return ""
 	}
 	return ipp.String()
@@ -157,7 +157,7 @@ type newRouteMessage struct {
 const tsTable = 52
 
 func (m *newRouteMessage) ignore() bool {
-	return m.Table == tsTable || tsaddr.IsTailscaleIP(m.Dst.IP)
+	return m.Table == tsTable || tsaddr.IsTailscaleIP(m.Dst.IP())
 }
 
 // newAddrMessage is a message for a new address being added.

+ 10 - 10
wgengine/netstack/netstack.go

@@ -179,7 +179,7 @@ func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap {
 	suffix := nm.MagicDNSSuffix()
 
 	if nm.Name != "" && len(nm.Addresses) > 0 {
-		ip := nm.Addresses[0].IP
+		ip := nm.Addresses[0].IP()
 		ret[strings.TrimRight(nm.Name, ".")] = ip
 		if dnsname.HasSuffix(nm.Name, suffix) {
 			ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip
@@ -187,7 +187,7 @@ func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap {
 	}
 	for _, p := range nm.Peers {
 		if p.Name != "" && len(p.Addresses) > 0 {
-			ip := p.Addresses[0].IP
+			ip := p.Addresses[0].IP()
 			ret[strings.TrimRight(p.Name, ".")] = ip
 			if dnsname.HasSuffix(p.Name, suffix) {
 				ret[dnsname.TrimSuffix(p.Name, suffix)] = ip
@@ -227,8 +227,8 @@ func (ns *Impl) removeSubnetAddress(ip netaddr.IP) {
 
 func ipPrefixToAddressWithPrefix(ipp netaddr.IPPrefix) tcpip.AddressWithPrefix {
 	return tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(ipp.IP.IPAddr().IP),
-		PrefixLen: int(ipp.Bits),
+		Address:   tcpip.Address(ipp.IP().IPAddr().IP),
+		PrefixLen: int(ipp.Bits()),
 	}
 }
 
@@ -322,7 +322,7 @@ func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error
 	// Try MagicDNS first, else otherwise a real DNS lookup.
 	ip := m[host]
 	if !ip.IsZero() {
-		return netaddr.IPPort{IP: ip, Port: uint16(port16)}, nil
+		return netaddr.IPPortFrom(ip, uint16(port16)), nil
 	}
 
 	// No MagicDNS name so try real DNS.
@@ -335,7 +335,7 @@ func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error
 		return netaddr.IPPort{}, fmt.Errorf("DNS lookup returned no results for %q", host)
 	}
 	ip, _ = netaddr.FromStdIP(ips[0])
-	return netaddr.IPPort{IP: ip, Port: uint16(port16)}, nil
+	return netaddr.IPPortFrom(ip, uint16(port16)), nil
 }
 
 func (ns *Impl) DialContextTCP(ctx context.Context, addr string) (*gonet.TCPConn, error) {
@@ -349,11 +349,11 @@ func (ns *Impl) DialContextTCP(ctx context.Context, addr string) (*gonet.TCPConn
 	}
 	remoteAddress := tcpip.FullAddress{
 		NIC:  nicID,
-		Addr: tcpip.Address(remoteIPPort.IP.IPAddr().IP),
-		Port: remoteIPPort.Port,
+		Addr: tcpip.Address(remoteIPPort.IP().IPAddr().IP),
+		Port: remoteIPPort.Port(),
 	}
 	var ipType tcpip.NetworkProtocolNumber
-	if remoteIPPort.IP.Is4() {
+	if remoteIPPort.IP().Is4() {
 		ipType = ipv4.ProtocolNumber
 	} else {
 		ipType = ipv6.ProtocolNumber
@@ -395,7 +395,7 @@ func (ns *Impl) isLocalIP(ip netaddr.IP) bool {
 }
 
 func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
-	if ns.onlySubnets && ns.isLocalIP(p.Dst.IP) {
+	if ns.onlySubnets && ns.isLocalIP(p.Dst.IP()) {
 		// In hybrid ("only subnets") mode, bail out early if
 		// the traffic is destined for an actual Tailscale
 		// address. The real host OS interface will handle it.

+ 4 - 4
wgengine/pendopen.go

@@ -115,8 +115,8 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.Wra
 	// Don't start timers tracking those. They won't succeed anyway. Avoids log spam
 	// like:
 	//    open-conn-track: timeout opening (100.115.73.60:52501 => 17.125.252.5:443); no associated peer node
-	if runtime.GOOS == "ios" && flow.Dst.Port == 443 && !tsaddr.IsTailscaleIP(flow.Dst.IP) {
-		if _, err := e.peerForIP(flow.Dst.IP); err != nil {
+	if runtime.GOOS == "ios" && flow.Dst.Port() == 443 && !tsaddr.IsTailscaleIP(flow.Dst.IP()) {
+		if _, err := e.peerForIP(flow.Dst.IP()); err != nil {
 			return
 		}
 	}
@@ -156,7 +156,7 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
 	}
 
 	// Diagnose why it might've timed out.
-	n, err := e.peerForIP(flow.Dst.IP)
+	n, err := e.peerForIP(flow.Dst.IP())
 	if err != nil {
 		e.logf("open-conn-track: timeout opening %v; peerForIP: %v", flow, err)
 		return
@@ -193,7 +193,7 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
 	if ps == nil {
 		onlyZeroRoute := true // whether peerForIP returned n only because its /0 route matched
 		for _, r := range n.AllowedIPs {
-			if r.Bits != 0 && r.Contains(flow.Dst.IP) {
+			if r.Bits() != 0 && r.Contains(flow.Dst.IP()) {
 				onlyZeroRoute = false
 				break
 			}

+ 14 - 17
wgengine/router/ifconfig_windows.go

@@ -324,16 +324,16 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
 	var firstGateway6 *net.IP
 	addresses := make([]*net.IPNet, 0, len(cfg.LocalAddrs))
 	for _, addr := range cfg.LocalAddrs {
-		if (addr.IP.Is4() && ipif4 == nil) || (addr.IP.Is6() && ipif6 == nil) {
+		if (addr.IP().Is4() && ipif4 == nil) || (addr.IP().Is6() && ipif6 == nil) {
 			// Can't program addresses for disabled protocol.
 			continue
 		}
 		ipnet := addr.IPNet()
 		addresses = append(addresses, ipnet)
 		gateway := ipnet.IP
-		if addr.IP.Is4() && firstGateway4 == nil {
+		if addr.IP().Is4() && firstGateway4 == nil {
 			firstGateway4 = &gateway
-		} else if addr.IP.Is6() && firstGateway6 == nil {
+		} else if addr.IP().Is6() && firstGateway6 == nil {
 			firstGateway6 = &gateway
 		}
 	}
@@ -342,12 +342,12 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
 	foundDefault4 := false
 	foundDefault6 := false
 	for _, route := range cfg.Routes {
-		if (route.IP.Is4() && ipif4 == nil) || (route.IP.Is6() && ipif6 == nil) {
+		if (route.IP().Is4() && ipif4 == nil) || (route.IP().Is6() && ipif6 == nil) {
 			// Can't program routes for disabled protocol.
 			continue
 		}
 
-		if route.IP.Is6() && firstGateway6 == nil {
+		if route.IP().Is6() && firstGateway6 == nil {
 			// Windows won't let us set IPv6 routes without having an
 			// IPv6 local address set. However, when we've configured
 			// a default route, we want to forcibly grab IPv6 traffic
@@ -357,16 +357,16 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
 			ipnet := &net.IPNet{tsaddr.Tailscale4To6Placeholder().IPAddr().IP, net.CIDRMask(128, 128)}
 			addresses = append(addresses, ipnet)
 			firstGateway6 = &ipnet.IP
-		} else if route.IP.Is4() && firstGateway4 == nil {
+		} else if route.IP().Is4() && firstGateway4 == nil {
 			// TODO: do same dummy behavior as v6?
 			return errors.New("due to a Windows limitation, one cannot have interface routes without an interface address")
 		}
 
 		ipn := route.IPNet()
 		var gateway net.IP
-		if route.IP.Is4() {
+		if route.IP().Is4() {
 			gateway = *firstGateway4
-		} else if route.IP.Is6() {
+		} else if route.IP().Is6() {
 			gateway = *firstGateway6
 		}
 		r := winipcfg.RouteData{
@@ -385,13 +385,13 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
 			// then the interface's IP won't be pingable.
 			continue
 		}
-		if route.IP.Is4() {
-			if route.Bits == 0 {
+		if route.IP().Is4() {
+			if route.Bits() == 0 {
 				foundDefault4 = true
 			}
 			r.NextHop = *firstGateway4
-		} else if route.IP.Is6() {
-			if route.Bits == 0 {
+		} else if route.IP().Is6() {
+			if route.Bits() == 0 {
 				foundDefault6 = true
 			}
 			r.NextHop = *firstGateway6
@@ -760,11 +760,8 @@ func filterRoutes(routes []*winipcfg.RouteData, dontDelete []netaddr.IPPrefix) [
 		if nr.IsSingleIP() {
 			continue
 		}
-		lastIP := nr.Range().To
-		ddm[netaddr.IPPrefix{
-			IP:   lastIP,
-			Bits: lastIP.BitLen(),
-		}] = true
+		lastIP := nr.Range().To()
+		ddm[netaddr.IPPrefixFrom(lastIP, lastIP.BitLen())] = true
 	}
 	filtered := make([]*winipcfg.RouteData, 0, len(routes))
 	for _, r := range routes {

+ 8 - 8
wgengine/router/router_linux.go

@@ -360,7 +360,7 @@ func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error {
 	}
 
 	for cidr := range r.addrs {
-		if err := r.addLoopbackRule(cidr.IP); err != nil {
+		if err := r.addLoopbackRule(cidr.IP()); err != nil {
 			return err
 		}
 	}
@@ -372,13 +372,13 @@ func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error {
 // address is already assigned to the interface, or if the addition
 // fails.
 func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
-	if !r.v6Available && addr.IP.Is6() {
+	if !r.v6Available && addr.IP().Is6() {
 		return nil
 	}
 	if err := r.cmd.run("ip", "addr", "add", addr.String(), "dev", r.tunname); err != nil {
 		return fmt.Errorf("adding address %q to tunnel interface: %w", addr, err)
 	}
-	if err := r.addLoopbackRule(addr.IP); err != nil {
+	if err := r.addLoopbackRule(addr.IP()); err != nil {
 		return err
 	}
 	return nil
@@ -388,10 +388,10 @@ func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
 // the address is not assigned to the interface, or if the removal
 // fails.
 func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
-	if !r.v6Available && addr.IP.Is6() {
+	if !r.v6Available && addr.IP().Is6() {
 		return nil
 	}
-	if err := r.delLoopbackRule(addr.IP); err != nil {
+	if err := r.delLoopbackRule(addr.IP()); err != nil {
 		return err
 	}
 	if err := r.cmd.run("ip", "addr", "del", addr.String(), "dev", r.tunname); err != nil {
@@ -463,7 +463,7 @@ func (r *linuxRouter) addThrowRoute(cidr netaddr.IPPrefix) error {
 }
 
 func (r *linuxRouter) addRouteDef(routeDef []string, cidr netaddr.IPPrefix) error {
-	if !r.v6Available && cidr.IP.Is6() {
+	if !r.v6Available && cidr.IP().Is6() {
 		return nil
 	}
 	args := append([]string{"ip", "route", "add"}, routeDef...)
@@ -490,7 +490,7 @@ func (r *linuxRouter) delThrowRoute(cidr netaddr.IPPrefix) error {
 }
 
 func (r *linuxRouter) delRouteDef(routeDef []string, cidr netaddr.IPPrefix) error {
-	if !r.v6Available && cidr.IP.Is6() {
+	if !r.v6Available && cidr.IP().Is6() {
 		return nil
 	}
 	args := append([]string{"ip", "route", "del"}, routeDef...)
@@ -520,7 +520,7 @@ func dashFam(ip netaddr.IP) string {
 }
 
 func (r *linuxRouter) hasRoute(routeDef []string, cidr netaddr.IPPrefix) (bool, error) {
-	args := append([]string{"ip", dashFam(cidr.IP), "route", "show"}, routeDef...)
+	args := append([]string{"ip", dashFam(cidr.IP()), "route", "show"}, routeDef...)
 	if r.ipRuleAvailable {
 		args = append(args, "table", tailscaleRouteTable)
 	}

+ 9 - 9
wgengine/router/router_openbsd.go

@@ -69,11 +69,11 @@ func (r *openbsdRouter) Set(cfg *Config) error {
 	localAddr4 := netaddr.IPPrefix{}
 	localAddr6 := netaddr.IPPrefix{}
 	for _, addr := range cfg.LocalAddrs {
-		if addr.IP.Is4() {
+		if addr.IP().Is4() {
 			numIPv4++
 			localAddr4 = addr
 		}
-		if addr.IP.Is6() {
+		if addr.IP().Is6() {
 			numIPv6++
 			localAddr6 = addr
 		}
@@ -98,7 +98,7 @@ func (r *openbsdRouter) Set(cfg *Config) error {
 
 			routedel := []string{"route", "-q", "-n",
 				"del", "-inet", r.local4.String(),
-				"-iface", r.local4.IP.String()}
+				"-iface", r.local4.IP().String()}
 			if out, err := cmd(routedel...).CombinedOutput(); err != nil {
 				r.logf("route del failed: %v: %v\n%s", routedel, err, out)
 				if errq == nil {
@@ -120,7 +120,7 @@ func (r *openbsdRouter) Set(cfg *Config) error {
 
 			routeadd := []string{"route", "-q", "-n",
 				"add", "-inet", localAddr4.String(),
-				"-iface", localAddr4.IP.String()}
+				"-iface", localAddr4.IP().String()}
 			if out, err := cmd(routeadd...).CombinedOutput(); err != nil {
 				r.logf("route add failed: %v: %v\n%s", routeadd, err, out)
 				if errq == nil {
@@ -134,7 +134,7 @@ func (r *openbsdRouter) Set(cfg *Config) error {
 		// in https://github.com/tailscale/tailscale/issues/1307 we made
 		// FreeBSD use a /48 for IPv6 addresses, which is nice because we
 		// don't need to additionally add routing entries. Do that here too.
-		localAddr6 = netaddr.IPPrefix{localAddr6.IP, 48}
+		localAddr6 = netaddr.IPPrefixFrom(localAddr6.IP(), 48)
 	}
 
 	if localAddr6 != r.local6 {
@@ -171,10 +171,10 @@ func (r *openbsdRouter) Set(cfg *Config) error {
 		if _, keep := newRoutes[route]; !keep {
 			net := route.IPNet()
 			nip := net.IP.Mask(net.Mask)
-			nstr := fmt.Sprintf("%v/%d", nip, route.Bits)
+			nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
 			routedel := []string{"route", "-q", "-n",
 				"del", "-inet", nstr,
-				"-iface", localAddr4.IP.String()}
+				"-iface", localAddr4.IP().String()}
 			out, err := cmd(routedel...).CombinedOutput()
 			if err != nil {
 				r.logf("route del failed: %v: %v\n%s", routedel, err, out)
@@ -188,10 +188,10 @@ func (r *openbsdRouter) Set(cfg *Config) error {
 		if _, exists := r.routes[route]; !exists {
 			net := route.IPNet()
 			nip := net.IP.Mask(net.Mask)
-			nstr := fmt.Sprintf("%v/%d", nip, route.Bits)
+			nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
 			routeadd := []string{"route", "-q", "-n",
 				"add", "-inet", nstr,
-				"-iface", localAddr4.IP.String()}
+				"-iface", localAddr4.IP().String()}
 			out, err := cmd(routeadd...).CombinedOutput()
 			if err != nil {
 				r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)

+ 6 - 6
wgengine/router/router_userspace_bsd.go

@@ -87,7 +87,7 @@ func (r *userspaceBSDRouter) Up() error {
 }
 
 func inet(p netaddr.IPPrefix) string {
-	if p.IP.Is6() {
+	if p.IP().Is6() {
 		return "inet6"
 	}
 	return "inet"
@@ -116,15 +116,15 @@ func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
 	}
 	for _, addr := range r.addrsToAdd(cfg.LocalAddrs) {
 		var arg []string
-		if runtime.GOOS == "freebsd" && addr.IP.Is6() && addr.Bits == 128 {
+		if runtime.GOOS == "freebsd" && addr.IP().Is6() && addr.Bits() == 128 {
 			// FreeBSD rejects tun addresses of the form fc00::1/128 -> fc00::1,
 			// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=218508
 			// Instead add our whole /48, which works because we use a /48 route.
 			// Full history: https://github.com/tailscale/tailscale/issues/1307
-			tmp := netaddr.IPPrefix{IP: addr.IP, Bits: 48}
+			tmp := netaddr.IPPrefixFrom(addr.IP(), 48)
 			arg = []string{"ifconfig", r.tunname, inet(tmp), tmp.String()}
 		} else {
-			arg = []string{"ifconfig", r.tunname, inet(addr), addr.String(), addr.IP.String()}
+			arg = []string{"ifconfig", r.tunname, inet(addr), addr.String(), addr.IP().String()}
 		}
 		out, err := cmd(arg...).CombinedOutput()
 		if err != nil {
@@ -148,7 +148,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
 		if _, keep := newRoutes[route]; !keep {
 			net := route.IPNet()
 			nip := net.IP.Mask(net.Mask)
-			nstr := fmt.Sprintf("%v/%d", nip, route.Bits)
+			nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
 			del := "del"
 			if version.OS() == "macOS" {
 				del = "delete"
@@ -168,7 +168,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
 		if _, exists := r.routes[route]; !exists {
 			net := route.IPNet()
 			nip := net.IP.Mask(net.Mask)
-			nstr := fmt.Sprintf("%v/%d", nip, route.Bits)
+			nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
 			routeadd := []string{"route", "-q", "-n",
 				"add", "-" + inet(route), nstr,
 				"-iface", r.tunname}

+ 1 - 1
wgengine/router/router_windows.go

@@ -91,7 +91,7 @@ func (r *winRouter) Set(cfg *Config) error {
 
 func hasDefaultRoute(routes []netaddr.IPPrefix) bool {
 	for _, route := range routes {
-		if route.Bits == 0 {
+		if route.Bits() == 0 {
 			return true
 		}
 	}

+ 12 - 12
wgengine/userspace.go

@@ -399,7 +399,7 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper)
 		isLocalAddr, ok := e.isLocalAddr.Load().(func(netaddr.IP) bool)
 		if !ok {
 			e.logf("[unexpected] e.isLocalAddr was nil, can't check for loopback packet")
-		} else if isLocalAddr(p.Dst.IP) {
+		} else if isLocalAddr(p.Dst.IP()) {
 			// macOS NetworkExtension directs packets destined to the
 			// tunnel's local IP address into the tunnel, instead of
 			// looping back within the kernel network stack. We have to
@@ -415,7 +415,7 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper)
 
 // handleDNS is an outbound pre-filter resolving Tailscale domains.
 func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
-	if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP {
+	if p.Dst.IP() == magicDNSIP && p.Dst.Port() == magicDNSPort && p.IPProto == ipproto.UDP {
 		err := e.dns.EnqueueRequest(append([]byte(nil), p.Payload()...), p.Src)
 		if err != nil {
 			e.logf("dns: enqueue: %v", err)
@@ -440,10 +440,10 @@ func (e *userspaceEngine) pollResolver() {
 		h := packet.UDP4Header{
 			IP4Header: packet.IP4Header{
 				Src: magicDNSIP,
-				Dst: to.IP,
+				Dst: to.IP(),
 			},
 			SrcPort: magicDNSPort,
-			DstPort: to.Port,
+			DstPort: to.Port(),
 		}
 		hlen := h.Len()
 
@@ -620,8 +620,8 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Publ
 		trackDisco = append(trackDisco, dk)
 		recentlyActive := false
 		for _, cidr := range p.AllowedIPs {
-			trackIPs = append(trackIPs, cidr.IP)
-			recentlyActive = recentlyActive || e.isActiveSince(dk, cidr.IP, activeCutoff)
+			trackIPs = append(trackIPs, cidr.IP())
+			recentlyActive = recentlyActive || e.isActiveSince(dk, cidr.IP(), activeCutoff)
 		}
 		if recentlyActive {
 			min.Peers = append(min.Peers, *p)
@@ -1156,8 +1156,8 @@ func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP
 		return netaddr.IP{}, errors.New("no netmap")
 	}
 	for _, a := range e.netMap.Addresses {
-		if a.IsSingleIP() && a.IP.BitLen() == dst.BitLen() {
-			return a.IP, nil
+		if a.IsSingleIP() && a.IP().BitLen() == dst.BitLen() {
+			return a.IP(), nil
 		}
 	}
 	if len(e.netMap.Addresses) == 0 {
@@ -1293,7 +1293,7 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
 	var bestInNM *tailcfg.Node
 	for _, p := range nm.Peers {
 		for _, a := range p.Addresses {
-			if a.IP == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
+			if a.IP() == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
 				return p, nil
 			}
 		}
@@ -1301,7 +1301,7 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
 			if !cidr.Contains(ip) {
 				continue
 			}
-			if bestInNMPrefix.IsZero() || cidr.Bits > bestInNMPrefix.Bits {
+			if bestInNMPrefix.IsZero() || cidr.Bits() > bestInNMPrefix.Bits() {
 				bestInNMPrefix = cidr
 				bestInNM = p
 			}
@@ -1319,7 +1319,7 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
 			if !cidr.Contains(ip) {
 				continue
 			}
-			if best.IsZero() || cidr.Bits > best.Bits {
+			if best.IsZero() || cidr.Bits() > best.Bits() {
 				best = cidr
 				bestKey = tailcfg.NodeKey(p.PublicKey)
 			}
@@ -1337,7 +1337,7 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
 	if bestInNM == nil {
 		return nil, nil
 	}
-	if bestInNMPrefix.Bits == 0 {
+	if bestInNMPrefix.Bits() == 0 {
 		return nil, errors.New("exit node found but not enabled")
 	}
 	return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix)

+ 1 - 1
wgengine/userspace_test.go

@@ -102,7 +102,7 @@ func TestUserspaceEngineReconfig(t *testing.T) {
 			Peers: []wgcfg.Peer{
 				{
 					AllowedIPs: []netaddr.IPPrefix{
-						{IP: netaddr.IPv4(100, 100, 99, 1), Bits: 32},
+						netaddr.IPPrefixFrom(netaddr.IPv4(100, 100, 99, 1), 32),
 					},
 					Endpoints: wgcfg.Endpoints{DiscoKey: dkFromHex(discoHex)},
 				},

+ 4 - 4
wgengine/wgcfg/nmcfg/nmcfg.go

@@ -37,7 +37,7 @@ func nodeDebugName(n *tailcfg.Node) string {
 // cidrIsSubnet reports whether cidr is a non-default-route subnet
 // exported by node that is not one of its own self addresses.
 func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool {
-	if cidr.Bits == 0 {
+	if cidr.Bits() == 0 {
 		return false
 	}
 	if !cidr.IsSingleIP() {
@@ -93,7 +93,7 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
 		}
 		didExitNodeWarn := false
 		for _, allowedIP := range peer.AllowedIPs {
-			if allowedIP.Bits == 0 && peer.StableID != exitNode {
+			if allowedIP.Bits() == 0 && peer.StableID != exitNode {
 				if didExitNodeWarn {
 					// Don't log about both the IPv4 /0 and IPv6 /0.
 					continue
@@ -104,11 +104,11 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
 				}
 				fmt.Fprintf(skippedUnselected, "%q (%v)", nodeDebugName(peer), peer.Key.ShortString())
 				continue
-			} else if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&netmap.AllowSingleHosts) == 0 {
+			} else if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP()) && (flags&netmap.AllowSingleHosts) == 0 {
 				if skippedIPs.Len() > 0 {
 					skippedIPs.WriteString(", ")
 				}
-				fmt.Fprintf(skippedIPs, "%v from %q (%v)", allowedIP.IP, nodeDebugName(peer), peer.Key.ShortString())
+				fmt.Fprintf(skippedIPs, "%v from %q (%v)", allowedIP.IP(), nodeDebugName(peer), peer.Key.ShortString())
 				continue
 			} else if cidrIsSubnet(peer, allowedIP) {
 				if (flags & netmap.AllowSubnetRoutes) == 0 {