mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
netcheck, tailcfg, interfaces, magicsock: survey UPnP, NAT-PMP, PCP
Don't do anything with UPnP, NAT-PMP, PCP yet, but see how common they are in the wild. Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
6196b7e658
commit
5c6d8e3053
@ -18,7 +18,6 @@
|
|||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
"tailscale.com/derp/derpmap"
|
"tailscale.com/derp/derpmap"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/interfaces"
|
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -51,11 +50,6 @@ func runNetcheck(ctx context.Context, args []string) error {
|
|||||||
if netcheckArgs.verbose {
|
if netcheckArgs.verbose {
|
||||||
c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
|
c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
|
||||||
c.Verbose = true
|
c.Verbose = true
|
||||||
if gw, ok := interfaces.LikelyHomeRouterIP(); ok {
|
|
||||||
c.Logf("likely home router: %v", gw)
|
|
||||||
} else {
|
|
||||||
c.Logf("no likely home router IP found")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
c.Logf = logger.Discard
|
c.Logf = logger.Discard
|
||||||
}
|
}
|
||||||
@ -123,6 +117,7 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
|||||||
}
|
}
|
||||||
fmt.Printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
|
fmt.Printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
|
||||||
fmt.Printf("\t* HairPinning: %v\n", report.HairPinning)
|
fmt.Printf("\t* HairPinning: %v\n", report.HairPinning)
|
||||||
|
fmt.Printf("\t* PortMapping: %v\n", portMapping(report))
|
||||||
|
|
||||||
// When DERP latency checking failed,
|
// When DERP latency checking failed,
|
||||||
// magicsock will try to pick the DERP server that
|
// magicsock will try to pick the DERP server that
|
||||||
@ -148,3 +143,20 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func portMapping(r *netcheck.Report) string {
|
||||||
|
if !r.AnyPortMappingChecked() {
|
||||||
|
return "not checked"
|
||||||
|
}
|
||||||
|
var got []string
|
||||||
|
if r.UPnP.EqualBool(true) {
|
||||||
|
got = append(got, "UPnP")
|
||||||
|
}
|
||||||
|
if r.PMP.EqualBool(true) {
|
||||||
|
got = append(got, "NAT-PMP")
|
||||||
|
}
|
||||||
|
if r.PCP.EqualBool(true) {
|
||||||
|
got = append(got, "PCP")
|
||||||
|
}
|
||||||
|
return strings.Join(got, ", ")
|
||||||
|
}
|
||||||
|
@ -234,12 +234,32 @@ func HTTPOfListener(ln net.Listener) string {
|
|||||||
|
|
||||||
// LikelyHomeRouterIP returns the likely IP of the residential router,
|
// LikelyHomeRouterIP returns the likely IP of the residential router,
|
||||||
// which will always be an IPv4 private address, if found.
|
// which will always be an IPv4 private address, if found.
|
||||||
|
// In addition, it returns the IP address of the current machine on
|
||||||
|
// the LAN using that gateway.
|
||||||
// This is used as the destination for UPnP, NAT-PMP, PCP, etc queries.
|
// This is used as the destination for UPnP, NAT-PMP, PCP, etc queries.
|
||||||
func LikelyHomeRouterIP() (ip netaddr.IP, ok bool) {
|
func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) {
|
||||||
if likelyHomeRouterIP != nil {
|
if likelyHomeRouterIP != nil {
|
||||||
return likelyHomeRouterIP()
|
gateway, ok = likelyHomeRouterIP()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ip, false
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ForeachInterfaceAddress(func(i Interface, ip netaddr.IP) {
|
||||||
|
if !i.IsUp() || ip.IsZero() || !myIP.IsZero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, prefix := range privatev4s {
|
||||||
|
if prefix.Contains(gateway) && prefix.Contains(ip) {
|
||||||
|
myIP = ip
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return gateway, myIP, !myIP.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPrivateIP(ip netaddr.IP) bool {
|
func isPrivateIP(ip netaddr.IP) bool {
|
||||||
@ -262,6 +282,7 @@ func mustCIDR(s string) netaddr.IPPrefix {
|
|||||||
private1 = mustCIDR("10.0.0.0/8")
|
private1 = mustCIDR("10.0.0.0/8")
|
||||||
private2 = mustCIDR("172.16.0.0/12")
|
private2 = mustCIDR("172.16.0.0/12")
|
||||||
private3 = mustCIDR("192.168.0.0/16")
|
private3 = mustCIDR("192.168.0.0/16")
|
||||||
|
privatev4s = []netaddr.IPPrefix{private1, private2, private3}
|
||||||
cgNAT = mustCIDR("100.64.0.0/10")
|
cgNAT = mustCIDR("100.64.0.0/10")
|
||||||
linkLocalIPv4 = mustCIDR("169.254.0.0/16")
|
linkLocalIPv4 = mustCIDR("169.254.0.0/16")
|
||||||
v6Global1 = mustCIDR("2000::/3")
|
v6Global1 = mustCIDR("2000::/3")
|
||||||
|
@ -49,6 +49,10 @@ func TestGetState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLikelyHomeRouterIP(t *testing.T) {
|
func TestLikelyHomeRouterIP(t *testing.T) {
|
||||||
ip, ok := LikelyHomeRouterIP()
|
gw, my, ok := LikelyHomeRouterIP()
|
||||||
t.Logf("got %v, %v", ip, ok)
|
if !ok {
|
||||||
|
t.Logf("no result")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Logf("myIP = %v; gw = %v", my, gw)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -21,6 +23,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tcnksm/go-httpstat"
|
"github.com/tcnksm/go-httpstat"
|
||||||
|
"go4.org/mem"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/derp/derphttp"
|
"tailscale.com/derp/derphttp"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
@ -34,15 +37,26 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Report struct {
|
type Report struct {
|
||||||
UDP bool // UDP works
|
UDP bool // UDP works
|
||||||
IPv6 bool // IPv6 works
|
IPv6 bool // IPv6 works
|
||||||
IPv4 bool // IPv4 works
|
IPv4 bool // IPv4 works
|
||||||
MappingVariesByDestIP opt.Bool // for IPv4
|
MappingVariesByDestIP opt.Bool // for IPv4
|
||||||
HairPinning opt.Bool // for IPv4
|
HairPinning opt.Bool // for IPv4
|
||||||
PreferredDERP int // or 0 for unknown
|
|
||||||
RegionLatency map[int]time.Duration // keyed by DERP Region ID
|
// UPnP is whether UPnP appears present on the LAN.
|
||||||
RegionV4Latency map[int]time.Duration // keyed by DERP Region ID
|
// Empty means not checked.
|
||||||
RegionV6Latency map[int]time.Duration // keyed by DERP Region ID
|
UPnP opt.Bool
|
||||||
|
// PMP is whether NAT-PMP appears present on the LAN.
|
||||||
|
// Empty means not checked.
|
||||||
|
PMP opt.Bool
|
||||||
|
// PCP is whether PCP appears present on the LAN.
|
||||||
|
// Empty means not checked.
|
||||||
|
PCP opt.Bool
|
||||||
|
|
||||||
|
PreferredDERP int // or 0 for unknown
|
||||||
|
RegionLatency map[int]time.Duration // keyed by DERP Region ID
|
||||||
|
RegionV4Latency map[int]time.Duration // keyed by DERP Region ID
|
||||||
|
RegionV6Latency map[int]time.Duration // keyed by DERP Region ID
|
||||||
|
|
||||||
GlobalV4 string // ip:port of global IPv4
|
GlobalV4 string // ip:port of global IPv4
|
||||||
GlobalV6 string // [ip]:port of global IPv6
|
GlobalV6 string // [ip]:port of global IPv6
|
||||||
@ -50,6 +64,11 @@ type Report struct {
|
|||||||
// TODO: update Clone when adding new fields
|
// TODO: update Clone when adding new fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnyPortMappingChecked reports whether any of UPnP, PMP, or PCP are non-empty.
|
||||||
|
func (r *Report) AnyPortMappingChecked() bool {
|
||||||
|
return r.UPnP != "" || r.PMP != "" || r.PCP != ""
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Report) Clone() *Report {
|
func (r *Report) Clone() *Report {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -434,6 +453,7 @@ type reportState struct {
|
|||||||
pc4Hair net.PacketConn
|
pc4Hair net.PacketConn
|
||||||
incremental bool // doing a lite, follow-up netcheck
|
incremental bool // doing a lite, follow-up netcheck
|
||||||
stopProbeCh chan struct{}
|
stopProbeCh chan struct{}
|
||||||
|
waitPortMap sync.WaitGroup
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
sentHairCheck bool
|
sentHairCheck bool
|
||||||
@ -599,6 +619,98 @@ func (rs *reportState) stopProbes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rs *reportState) setOptBool(b *opt.Bool, v bool) {
|
||||||
|
rs.mu.Lock()
|
||||||
|
defer rs.mu.Unlock()
|
||||||
|
b.Set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *reportState) probePortMapServices() {
|
||||||
|
defer rs.waitPortMap.Done()
|
||||||
|
gw, myIP, ok := interfaces.LikelyHomeRouterIP()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.setOptBool(&rs.report.UPnP, false)
|
||||||
|
rs.setOptBool(&rs.report.PMP, false)
|
||||||
|
rs.setOptBool(&rs.report.PCP, false)
|
||||||
|
|
||||||
|
port1900 := netaddr.IPPort{IP: gw, Port: 1900}.UDPAddr()
|
||||||
|
port5351 := netaddr.IPPort{IP: gw, Port: 5351}.UDPAddr()
|
||||||
|
|
||||||
|
rs.c.logf("probePortMapServices: me %v -> gw %v", myIP, gw)
|
||||||
|
|
||||||
|
// Create a UDP4 socket used just for querying for UPnP, NAT-PMP, and PCP.
|
||||||
|
uc, err := netns.Listener().ListenPacket(context.Background(), "udp4", ":0")
|
||||||
|
if err != nil {
|
||||||
|
rs.c.logf("probePortMapServices: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer uc.Close()
|
||||||
|
tempPort := uc.LocalAddr().(*net.UDPAddr).Port
|
||||||
|
uc.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||||
|
|
||||||
|
// Send request packets for all three protocols.
|
||||||
|
uc.WriteTo(uPnPPacket, port1900)
|
||||||
|
uc.WriteTo(pmpPacket, port5351)
|
||||||
|
uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351)
|
||||||
|
|
||||||
|
res := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
n, addr, err := uc.ReadFrom(res)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch addr.(*net.UDPAddr).Port {
|
||||||
|
case 1900:
|
||||||
|
if mem.Contains(mem.B(res[:n]), mem.S(":InternetGatewayDevice:")) {
|
||||||
|
rs.setOptBool(&rs.report.UPnP, true)
|
||||||
|
}
|
||||||
|
case 5351:
|
||||||
|
if n == 12 && res[0] == 0x00 { // right length and version 0
|
||||||
|
rs.setOptBool(&rs.report.PMP, true)
|
||||||
|
}
|
||||||
|
if n == 60 && res[0] == 0x02 { // right length and version 2
|
||||||
|
rs.setOptBool(&rs.report.PCP, true)
|
||||||
|
// Delete the mapping.
|
||||||
|
uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pmpPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request"
|
||||||
|
|
||||||
|
var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
|
||||||
|
"HOST: 239.255.255.250:1900\r\n" +
|
||||||
|
"ST: ssdp:all\r\n" +
|
||||||
|
"MAN: \"ssdp:discover\"\r\n" +
|
||||||
|
"MX: 2\r\n\r\n")
|
||||||
|
|
||||||
|
var v4unspec, _ = netaddr.ParseIP("0.0.0.0")
|
||||||
|
|
||||||
|
func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
|
||||||
|
const udpProtoNumber = 17
|
||||||
|
lifetimeSeconds := uint32(1)
|
||||||
|
if delete {
|
||||||
|
lifetimeSeconds = 0
|
||||||
|
}
|
||||||
|
const opMap = 1
|
||||||
|
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8)
|
||||||
|
pkt[0] = 2 // version
|
||||||
|
pkt[1] = opMap
|
||||||
|
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
|
||||||
|
myIP16 := myIP.As16()
|
||||||
|
copy(pkt[8:], myIP16[:])
|
||||||
|
rand.Read(pkt[24 : 24+12])
|
||||||
|
pkt[36] = udpProtoNumber
|
||||||
|
binary.BigEndian.PutUint16(pkt[40:], uint16(mapToLocalPort))
|
||||||
|
v4unspec16 := v4unspec.As16()
|
||||||
|
copy(pkt[40:], v4unspec16[:])
|
||||||
|
return pkt
|
||||||
|
}
|
||||||
|
|
||||||
func newReport() *Report {
|
func newReport() *Report {
|
||||||
return &Report{
|
return &Report{
|
||||||
RegionLatency: make(map[int]time.Duration),
|
RegionLatency: make(map[int]time.Duration),
|
||||||
@ -671,6 +783,9 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
|
|||||||
}
|
}
|
||||||
defer rs.pc4Hair.Close()
|
defer rs.pc4Hair.Close()
|
||||||
|
|
||||||
|
rs.waitPortMap.Add(1)
|
||||||
|
go rs.probePortMapServices()
|
||||||
|
|
||||||
// At least the Apple Airport Extreme doesn't allow hairpin
|
// At least the Apple Airport Extreme doesn't allow hairpin
|
||||||
// sends from a private socket until it's seen traffic from
|
// sends from a private socket until it's seen traffic from
|
||||||
// that src IP:port to something else out on the internet.
|
// that src IP:port to something else out on the internet.
|
||||||
@ -738,6 +853,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
rs.waitHairCheck(ctx)
|
rs.waitHairCheck(ctx)
|
||||||
|
rs.waitPortMap.Wait()
|
||||||
rs.stopTimers()
|
rs.stopTimers()
|
||||||
|
|
||||||
// Try HTTPS latency check if all STUN probes failed due to UDP presumably being blocked.
|
// Try HTTPS latency check if all STUN probes failed due to UDP presumably being blocked.
|
||||||
@ -861,6 +977,11 @@ func (c *Client) logConciseReport(r *Report, dm *tailcfg.DERPMap) {
|
|||||||
fmt.Fprintf(w, " v6=%v", r.IPv6)
|
fmt.Fprintf(w, " v6=%v", r.IPv6)
|
||||||
fmt.Fprintf(w, " mapvarydest=%v", r.MappingVariesByDestIP)
|
fmt.Fprintf(w, " mapvarydest=%v", r.MappingVariesByDestIP)
|
||||||
fmt.Fprintf(w, " hair=%v", r.HairPinning)
|
fmt.Fprintf(w, " hair=%v", r.HairPinning)
|
||||||
|
if r.AnyPortMappingChecked() {
|
||||||
|
fmt.Fprintf(w, " portmap=%v%v%v", conciseOptBool(r.UPnP, "U"), conciseOptBool(r.PMP, "M"), conciseOptBool(r.PCP, "C"))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, " portmap=?")
|
||||||
|
}
|
||||||
if r.GlobalV4 != "" {
|
if r.GlobalV4 != "" {
|
||||||
fmt.Fprintf(w, " v4a=%v", r.GlobalV4)
|
fmt.Fprintf(w, " v4a=%v", r.GlobalV4)
|
||||||
}
|
}
|
||||||
@ -1069,3 +1190,17 @@ func maxDurationValue(m map[int]time.Duration) (max time.Duration) {
|
|||||||
}
|
}
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func conciseOptBool(b opt.Bool, trueVal string) string {
|
||||||
|
if b == "" {
|
||||||
|
return "_"
|
||||||
|
}
|
||||||
|
v, ok := b.Get()
|
||||||
|
if !ok {
|
||||||
|
return "x"
|
||||||
|
}
|
||||||
|
if v {
|
||||||
|
return trueVal
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -100,6 +100,9 @@ func TestWorksWhenUDPBlocked(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
want := newReport()
|
want := newReport()
|
||||||
|
r.UPnP = ""
|
||||||
|
r.PMP = ""
|
||||||
|
r.PCP = ""
|
||||||
|
|
||||||
if !reflect.DeepEqual(r, want) {
|
if !reflect.DeepEqual(r, want) {
|
||||||
t.Errorf("mismatch\n got: %+v\nwant: %+v\n", r, want)
|
t.Errorf("mismatch\n got: %+v\nwant: %+v\n", r, want)
|
||||||
@ -463,7 +466,7 @@ func TestLogConciseReport(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "no_udp",
|
name: "no_udp",
|
||||||
r: &Report{},
|
r: &Report{},
|
||||||
want: "udp=false v4=false v6=false mapvarydest= hair= derp=0",
|
want: "udp=false v4=false v6=false mapvarydest= hair= portmap=? derp=0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ipv4_one_region",
|
name: "ipv4_one_region",
|
||||||
@ -478,7 +481,7 @@ func TestLogConciseReport(t *testing.T) {
|
|||||||
1: 10 * ms,
|
1: 10 * ms,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: "udp=true v6=false mapvarydest= hair= derp=1 derpdist=1v4:10ms",
|
want: "udp=true v6=false mapvarydest= hair= portmap=? derp=1 derpdist=1v4:10ms",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ipv4_all_region",
|
name: "ipv4_all_region",
|
||||||
@ -497,7 +500,7 @@ func TestLogConciseReport(t *testing.T) {
|
|||||||
3: 30 * ms,
|
3: 30 * ms,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: "udp=true v6=false mapvarydest= hair= derp=1 derpdist=1v4:10ms,2v4:20ms,3v4:30ms",
|
want: "udp=true v6=false mapvarydest= hair= portmap=? derp=1 derpdist=1v4:10ms,2v4:20ms,3v4:30ms",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ipboth_all_region",
|
name: "ipboth_all_region",
|
||||||
@ -522,7 +525,27 @@ func TestLogConciseReport(t *testing.T) {
|
|||||||
3: 30 * ms,
|
3: 30 * ms,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: "udp=true v6=true mapvarydest= hair= derp=1 derpdist=1v4:10ms,1v6:10ms,2v4:20ms,2v6:20ms,3v4:30ms,3v6:30ms",
|
want: "udp=true v6=true mapvarydest= hair= portmap=? derp=1 derpdist=1v4:10ms,1v6:10ms,2v4:20ms,2v6:20ms,3v4:30ms,3v6:30ms",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "portmap_all",
|
||||||
|
r: &Report{
|
||||||
|
UDP: true,
|
||||||
|
UPnP: "true",
|
||||||
|
PMP: "true",
|
||||||
|
PCP: "true",
|
||||||
|
},
|
||||||
|
want: "udp=true v4=false v6=false mapvarydest= hair= portmap=UMC derp=0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "portmap_some",
|
||||||
|
r: &Report{
|
||||||
|
UDP: true,
|
||||||
|
UPnP: "true",
|
||||||
|
PMP: "false",
|
||||||
|
PCP: "true",
|
||||||
|
},
|
||||||
|
want: "udp=true v4=false v6=false mapvarydest= hair= portmap=UC derp=0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -310,6 +310,18 @@ type NetInfo struct {
|
|||||||
// WorkingUDP is whether UDP works.
|
// WorkingUDP is whether UDP works.
|
||||||
WorkingUDP opt.Bool
|
WorkingUDP opt.Bool
|
||||||
|
|
||||||
|
// UPnP is whether UPnP appears present on the LAN.
|
||||||
|
// Empty means not checked.
|
||||||
|
UPnP opt.Bool
|
||||||
|
|
||||||
|
// PMP is whether NAT-PMP appears present on the LAN.
|
||||||
|
// Empty means not checked.
|
||||||
|
PMP opt.Bool
|
||||||
|
|
||||||
|
// PCP is whether PCP appears present on the LAN.
|
||||||
|
// Empty means not checked.
|
||||||
|
PCP opt.Bool
|
||||||
|
|
||||||
// PreferredDERP is this node's preferred DERP server
|
// PreferredDERP is this node's preferred DERP server
|
||||||
// for incoming traffic. The node might be be temporarily
|
// for incoming traffic. The node might be be temporarily
|
||||||
// connected to multiple DERP servers (to send to other nodes)
|
// connected to multiple DERP servers (to send to other nodes)
|
||||||
@ -338,9 +350,32 @@ func (ni *NetInfo) String() string {
|
|||||||
if ni == nil {
|
if ni == nil {
|
||||||
return "NetInfo(nil)"
|
return "NetInfo(nil)"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("NetInfo{varies=%v hairpin=%v ipv6=%v udp=%v derp=#%v link=%q}",
|
return fmt.Sprintf("NetInfo{varies=%v hairpin=%v ipv6=%v udp=%v derp=#%v portmap=%v link=%q}",
|
||||||
ni.MappingVariesByDestIP, ni.HairPinning, ni.WorkingIPv6,
|
ni.MappingVariesByDestIP, ni.HairPinning, ni.WorkingIPv6,
|
||||||
ni.WorkingUDP, ni.PreferredDERP, ni.LinkType)
|
ni.WorkingUDP, ni.PreferredDERP,
|
||||||
|
ni.portMapSummary(),
|
||||||
|
ni.LinkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ni *NetInfo) portMapSummary() string {
|
||||||
|
if ni.UPnP == "" && ni.PMP == "" && ni.PCP == "" {
|
||||||
|
return "na"
|
||||||
|
}
|
||||||
|
return conciseOptBool(ni.UPnP, "U") + conciseOptBool(ni.PMP, "M") + conciseOptBool(ni.PCP, "C")
|
||||||
|
}
|
||||||
|
|
||||||
|
func conciseOptBool(b opt.Bool, trueVal string) string {
|
||||||
|
if b == "" {
|
||||||
|
return "_"
|
||||||
|
}
|
||||||
|
v, ok := b.Get()
|
||||||
|
if !ok {
|
||||||
|
return "x"
|
||||||
|
}
|
||||||
|
if v {
|
||||||
|
return trueVal
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// BasicallyEqual reports whether ni and ni2 are basically equal, ignoring
|
// BasicallyEqual reports whether ni and ni2 are basically equal, ignoring
|
||||||
@ -356,6 +391,9 @@ func (ni *NetInfo) BasicallyEqual(ni2 *NetInfo) bool {
|
|||||||
ni.HairPinning == ni2.HairPinning &&
|
ni.HairPinning == ni2.HairPinning &&
|
||||||
ni.WorkingIPv6 == ni2.WorkingIPv6 &&
|
ni.WorkingIPv6 == ni2.WorkingIPv6 &&
|
||||||
ni.WorkingUDP == ni2.WorkingUDP &&
|
ni.WorkingUDP == ni2.WorkingUDP &&
|
||||||
|
ni.UPnP == ni2.UPnP &&
|
||||||
|
ni.PMP == ni2.PMP &&
|
||||||
|
ni.PCP == ni2.PCP &&
|
||||||
ni.PreferredDERP == ni2.PreferredDERP &&
|
ni.PreferredDERP == ni2.PreferredDERP &&
|
||||||
ni.LinkType == ni2.LinkType
|
ni.LinkType == ni2.LinkType
|
||||||
}
|
}
|
||||||
|
@ -329,6 +329,9 @@ func TestNetInfoFields(t *testing.T) {
|
|||||||
"HairPinning",
|
"HairPinning",
|
||||||
"WorkingIPv6",
|
"WorkingIPv6",
|
||||||
"WorkingUDP",
|
"WorkingUDP",
|
||||||
|
"UPnP",
|
||||||
|
"PMP",
|
||||||
|
"PCP",
|
||||||
"PreferredDERP",
|
"PreferredDERP",
|
||||||
"LinkType",
|
"LinkType",
|
||||||
"DERPLatency",
|
"DERPLatency",
|
||||||
|
@ -389,6 +389,9 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) {
|
|||||||
DERPLatency: map[string]float64{},
|
DERPLatency: map[string]float64{},
|
||||||
MappingVariesByDestIP: report.MappingVariesByDestIP,
|
MappingVariesByDestIP: report.MappingVariesByDestIP,
|
||||||
HairPinning: report.HairPinning,
|
HairPinning: report.HairPinning,
|
||||||
|
UPnP: report.UPnP,
|
||||||
|
PMP: report.PMP,
|
||||||
|
PCP: report.PCP,
|
||||||
}
|
}
|
||||||
for rid, d := range report.RegionV4Latency {
|
for rid, d := range report.RegionV4Latency {
|
||||||
ni.DERPLatency[fmt.Sprintf("%d-v4", rid)] = d.Seconds()
|
ni.DERPLatency[fmt.Sprintf("%d-v4", rid)] = d.Seconds()
|
||||||
|
Loading…
Reference in New Issue
Block a user