Compare commits
1 Commits
bradfitz/p
...
upnpdebug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e0b588618 |
@@ -14,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/peterbourgon/ff/v2/ffcli"
|
||||
"github.com/toqueteos/webbrowser"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
@@ -61,7 +63,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
if statusArgs.json {
|
||||
if statusArgs.active {
|
||||
for peer, ps := range st.Peer {
|
||||
if !ps.Active {
|
||||
if !peerActive(ps) {
|
||||
delete(st.Peer, peer)
|
||||
}
|
||||
}
|
||||
@@ -129,6 +131,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
var buf bytes.Buffer
|
||||
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
|
||||
printPS := func(ps *ipnstate.PeerStatus) {
|
||||
active := peerActive(ps)
|
||||
f("%-15s %-20s %-12s %-7s ",
|
||||
firstIPString(ps.TailscaleIPs),
|
||||
dnsOrQuoteHostname(st, ps),
|
||||
@@ -137,7 +140,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
)
|
||||
relay := ps.Relay
|
||||
anyTraffic := ps.TxBytes != 0 || ps.RxBytes != 0
|
||||
if !ps.Active {
|
||||
if !active {
|
||||
if ps.ExitNode {
|
||||
f("idle; exit node")
|
||||
} else if anyTraffic {
|
||||
@@ -176,7 +179,8 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
}
|
||||
ipnstate.SortPeers(peers)
|
||||
for _, ps := range peers {
|
||||
if statusArgs.active && !ps.Active {
|
||||
active := peerActive(ps)
|
||||
if statusArgs.active && !active {
|
||||
continue
|
||||
}
|
||||
printPS(ps)
|
||||
@@ -186,6 +190,13 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// peerActive reports whether ps has recent activity.
|
||||
//
|
||||
// TODO: have the server report this bool instead.
|
||||
func peerActive(ps *ipnstate.PeerStatus) bool {
|
||||
return !ps.LastWrite.IsZero() && mono.Since(ps.LastWrite) < 2*time.Minute
|
||||
}
|
||||
|
||||
func dnsOrQuoteHostname(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
|
||||
baseName := dnsname.TrimSuffix(ps.DNSName, st.MagicDNSSuffix)
|
||||
if baseName != "" {
|
||||
|
||||
@@ -7,7 +7,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli
|
||||
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
|
||||
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2
|
||||
github.com/tailscale/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||
github.com/tailscale/goupnp/httpu from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||
@@ -49,7 +49,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||
tailscale.com/types/empty from tailscale.com/ipn
|
||||
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
||||
@@ -57,7 +57,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/types/netmap from tailscale.com/ipn
|
||||
tailscale.com/types/opt from tailscale.com/net/netcheck+
|
||||
tailscale.com/types/pad32 from tailscale.com/derp
|
||||
tailscale.com/types/persist from tailscale.com/ipn
|
||||
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/types/structs from tailscale.com/ipn+
|
||||
|
||||
@@ -207,20 +207,6 @@ func debugPortmap(ctx context.Context) error {
|
||||
defer cancel()
|
||||
|
||||
portmapper.VerboseLogs = true
|
||||
switch os.Getenv("TS_DEBUG_PORTMAP_TYPE") {
|
||||
case "":
|
||||
case "pmp":
|
||||
portmapper.DisablePCP = true
|
||||
portmapper.DisableUPnP = true
|
||||
case "pcp":
|
||||
portmapper.DisablePMP = true
|
||||
portmapper.DisableUPnP = true
|
||||
case "upnp":
|
||||
portmapper.DisablePCP = true
|
||||
portmapper.DisablePMP = true
|
||||
default:
|
||||
log.Fatalf("TS_DEBUG_PORTMAP_TYPE must be one of pmp,pcp,upnp")
|
||||
}
|
||||
|
||||
done := make(chan bool, 1)
|
||||
|
||||
@@ -264,13 +250,6 @@ func debugPortmap(ctx context.Context) error {
|
||||
}
|
||||
logf("gw=%v; self=%v", gw, selfIP)
|
||||
|
||||
uc, err := net.ListenPacket("udp", "0.0.0.0:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer uc.Close()
|
||||
c.SetLocalPort(uint16(uc.LocalAddr().(*net.UDPAddr).Port))
|
||||
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Probe: %v", err)
|
||||
@@ -282,6 +261,13 @@ func debugPortmap(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
uc, err := net.ListenPacket("udp", "0.0.0.0:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer uc.Close()
|
||||
c.SetLocalPort(uint16(uc.LocalAddr().(*net.UDPAddr).Port))
|
||||
|
||||
if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok {
|
||||
logf("mapping: %v", ext)
|
||||
} else {
|
||||
|
||||
@@ -10,10 +10,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
|
||||
github.com/golang/snappy from github.com/klauspost/compress/zstd
|
||||
github.com/google/btree from inet.af/netstack/tcpip/header+
|
||||
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
|
||||
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
@@ -27,16 +23,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2
|
||||
github.com/tailscale/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||
github.com/tailscale/goupnp/httpu from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||
L github.com/u-root/uio/rand from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/u-root/uio/ubinary from github.com/u-root/uio/uio
|
||||
L github.com/u-root/uio/uio from github.com/insomniacslk/dhcp/dhcpv4+
|
||||
💣 go4.org/intern from inet.af/netaddr
|
||||
💣 go4.org/mem from tailscale.com/derp+
|
||||
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
|
||||
@@ -73,7 +66,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
inet.af/netstack/tcpip/network/hash from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/internal/fragmentation from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/internal/ip from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/wgengine/netstack+
|
||||
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/ports from inet.af/netstack/tcpip/stack+
|
||||
inet.af/netstack/tcpip/seqnum from inet.af/netstack/tcpip/header+
|
||||
@@ -128,7 +121,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
|
||||
💣 tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/safesocket from tailscale.com/ipn/ipnserver+
|
||||
@@ -147,7 +140,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/types/netmap from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/types/opt from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/pad32 from tailscale.com/net/tstun+
|
||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||
|
||||
@@ -68,13 +68,9 @@ func defaultTunName() string {
|
||||
}
|
||||
|
||||
var args struct {
|
||||
// tunname is a /dev/net/tun tunnel name ("tailscale0"), the
|
||||
// string "userspace-networking", "tap:TAPNAME[:BRIDGENAME]"
|
||||
// or comma-separated list thereof.
|
||||
tunname string
|
||||
|
||||
cleanup bool
|
||||
debug string
|
||||
tunname string // tun name, "userspace-networking", or comma-separated list thereof
|
||||
port uint16
|
||||
statepath string
|
||||
socketpath string
|
||||
@@ -356,12 +352,6 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.
|
||||
return nil, false, err
|
||||
}
|
||||
conf.Tun = dev
|
||||
if strings.HasPrefix(name, "tap:") {
|
||||
conf.IsTAP = true
|
||||
e, err := wgengine.NewUserspaceEngine(logf, conf)
|
||||
return e, false, err
|
||||
}
|
||||
|
||||
r, err := router.New(logf, dev, linkMon)
|
||||
if err != nil {
|
||||
dev.Close()
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"tailscale.com/metrics"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/pad32"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
@@ -77,6 +76,13 @@ const (
|
||||
writeTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
const host64bit = (^uint(0) >> 32) & 1 // 1 on 64-bit, 0 on 32-bit
|
||||
|
||||
// pad32bit is 4 on 32-bit machines and 0 on 64-bit.
|
||||
// It exists so the Server struct's atomic fields can be aligned to 8
|
||||
// byte boundaries. (As tested by GOARCH=386 go test, etc)
|
||||
const pad32bit = 4 - host64bit*4 // 0 on 64-bit, 4 on 32-bit
|
||||
|
||||
// Server is a DERP server.
|
||||
type Server struct {
|
||||
// WriteTimeout, if non-zero, specifies how long to wait
|
||||
@@ -92,20 +98,20 @@ type Server struct {
|
||||
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
|
||||
|
||||
// Counters:
|
||||
_ pad32.Four
|
||||
_ [pad32bit]byte
|
||||
packetsSent, bytesSent expvar.Int
|
||||
packetsRecv, bytesRecv expvar.Int
|
||||
packetsRecvByKind metrics.LabelMap
|
||||
packetsRecvDisco *expvar.Int
|
||||
packetsRecvOther *expvar.Int
|
||||
_ pad32.Four
|
||||
_ [pad32bit]byte
|
||||
packetsDropped expvar.Int
|
||||
packetsDroppedReason metrics.LabelMap
|
||||
packetsDroppedReasonCounters []*expvar.Int // indexed by dropReason
|
||||
packetsDroppedType metrics.LabelMap
|
||||
packetsDroppedTypeDisco *expvar.Int
|
||||
packetsDroppedTypeOther *expvar.Int
|
||||
_ pad32.Four
|
||||
_ [pad32bit]byte
|
||||
packetsForwardedOut expvar.Int
|
||||
packetsForwardedIn expvar.Int
|
||||
peerGoneFrames expvar.Int // number of peer gone frames sent
|
||||
|
||||
1
go.mod
1
go.mod
@@ -19,7 +19,6 @@ require (
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/goreleaser/nfpm v1.10.3
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.12.2
|
||||
|
||||
14
go.sum
14
go.sum
@@ -102,7 +102,6 @@ github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
@@ -298,7 +297,6 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
@@ -306,8 +304,6 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e h1:sgh63o+pm5kcdrgyYaCIoeD7mccyL6MscVmy+DvY6C4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -330,7 +326,6 @@ github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/rasw
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
@@ -396,7 +391,6 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mbilski/exhaustivestruct v1.1.0 h1:4ykwscnAFeHJruT+EY3M3vdeP8uXMh0VV2E61iR7XD8=
|
||||
github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
@@ -412,8 +406,6 @@ github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuri
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/mdlayher/netlink v1.4.1 h1:I154BCU+mKlIf7BgcAJB2r7QjveNPty6uNY1g9ChVfI=
|
||||
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697 h1:PBb7ld5cQGfxHF2pKvb/ydtuPwdRaltGI4e0QSCuiNI=
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
|
||||
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00 h1:qEtkL8n1DAHpi5/AOgAckwGQUlMe4+jhL/GMt+GKIks=
|
||||
@@ -612,8 +604,6 @@ github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLK
|
||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
|
||||
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
|
||||
@@ -709,7 +699,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
@@ -766,11 +755,9 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -790,7 +777,6 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
@@ -90,19 +91,12 @@ type PeerStatus struct {
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
Created time.Time // time registered with tailcontrol
|
||||
LastWrite time.Time // time last packet sent
|
||||
LastWrite mono.Time // time last packet sent
|
||||
LastSeen time.Time // last seen to tailcontrol
|
||||
LastHandshake time.Time // with local wireguard
|
||||
KeepAlive bool
|
||||
ExitNode bool // true if this is the currently selected exit node.
|
||||
|
||||
// Active is whether the node was recently active. The
|
||||
// definition is somewhat undefined but has historically and
|
||||
// currently means that there was some packet sent to this
|
||||
// peer in the past two minutes. That definition is subject to
|
||||
// change.
|
||||
Active bool
|
||||
|
||||
PeerAPIURL []string
|
||||
Capabilities []string `json:",omitempty"`
|
||||
|
||||
@@ -284,9 +278,6 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
|
||||
if st.ShareeNode {
|
||||
e.ShareeNode = true
|
||||
}
|
||||
if st.Active {
|
||||
e.Active = true
|
||||
}
|
||||
}
|
||||
|
||||
type StatusUpdater interface {
|
||||
@@ -330,7 +321,7 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
|
||||
f("<tr><th>Peer</th><th>OS</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Connection</th></tr>\n")
|
||||
f("</thead>\n<tbody>\n")
|
||||
|
||||
now := time.Now()
|
||||
now := mono.Now()
|
||||
|
||||
var peers []*PeerStatus
|
||||
for _, peer := range st.Peers() {
|
||||
@@ -387,7 +378,9 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
|
||||
)
|
||||
f("<td>")
|
||||
|
||||
if ps.Active {
|
||||
// TODO: let server report this active bool instead
|
||||
active := !ps.LastWrite.IsZero() && mono.Since(ps.LastWrite) < 2*time.Minute
|
||||
if active {
|
||||
if ps.Relay != "" && ps.CurAddr == "" {
|
||||
f("relay <b>%s</b>", html.EscapeString(ps.Relay))
|
||||
} else if ps.CurAddr != "" {
|
||||
|
||||
@@ -15,12 +15,6 @@ import (
|
||||
|
||||
type upnpClient interface{}
|
||||
|
||||
type uPnPDiscoResponse struct{}
|
||||
|
||||
func parseUPnPDiscoResponse([]byte) (uPnPDiscoResponse, error) {
|
||||
return uPnPDiscoResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *Client) getUPnPPortMapping(
|
||||
ctx context.Context,
|
||||
gw netaddr.IP,
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
// TestIGD is an IGD (Intenet Gateway Device) for testing. It supports fake
|
||||
// implementations of NAT-PMP, PCP, and/or UPnP to test clients against.
|
||||
type TestIGD struct {
|
||||
upnpConn net.PacketConn // for UPnP discovery
|
||||
pxpConn net.PacketConn // for NAT-PMP and/or PCP
|
||||
ts *httptest.Server
|
||||
|
||||
doPMP bool
|
||||
doPCP bool
|
||||
doUPnP bool // TODO: more options for 3 flavors of UPnP services
|
||||
}
|
||||
|
||||
func NewTestIGD() (*TestIGD, error) {
|
||||
d := &TestIGD{
|
||||
doPMP: true,
|
||||
doPCP: true,
|
||||
doUPnP: true,
|
||||
}
|
||||
var err error
|
||||
if d.upnpConn, err = net.ListenPacket("udp", "127.0.0.1:1900"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.pxpConn, err = net.ListenPacket("udp", "127.0.0.1:5351"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.ts = httptest.NewServer(http.HandlerFunc(d.serveUPnPHTTP))
|
||||
go d.serveUPnPDiscovery()
|
||||
go d.servePxP()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *TestIGD) Close() error {
|
||||
d.ts.Close()
|
||||
d.upnpConn.Close()
|
||||
d.pxpConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *TestIGD) serveUPnPHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r) // TODO
|
||||
}
|
||||
|
||||
func (d *TestIGD) serveUPnPDiscovery() {
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
n, addr, err := d.upnpConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pkt := buf[:n]
|
||||
_, _ = pkt, addr // TODO
|
||||
}
|
||||
}
|
||||
|
||||
// servePxP serves NAT-PMP and PCP, which share a port number.
|
||||
func (d *TestIGD) servePxP() {
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
n, addr, err := d.pxpConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pkt := buf[:n]
|
||||
_, _ = pkt, addr // TODO
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,6 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// Debub knobs for "tailscaled debug --portmap".
|
||||
var (
|
||||
VerboseLogs bool
|
||||
DisableUPnP bool
|
||||
DisablePMP bool
|
||||
DisablePCP bool
|
||||
)
|
||||
|
||||
// References:
|
||||
//
|
||||
// NAT-PMP: https://tools.ietf.org/html/rfc6886
|
||||
@@ -76,7 +68,7 @@ type Client struct {
|
||||
|
||||
uPnPSawTime time.Time // time we last saw UPnP was available
|
||||
uPnPMeta uPnPDiscoResponse // Location header from UPnP UDP discovery response
|
||||
uPnPHTTPClient *http.Client // netns-configured HTTP client for UPnP; nil until needed
|
||||
uPnPHTTPClient *http.Client // nil until needed
|
||||
|
||||
localPort uint16
|
||||
|
||||
@@ -223,7 +215,6 @@ func (c *Client) invalidateMappingsLocked(releaseOld bool) {
|
||||
c.pmpPubIPTime = time.Time{}
|
||||
c.pcpSawTime = time.Time{}
|
||||
c.uPnPSawTime = time.Time{}
|
||||
c.uPnPMeta = uPnPDiscoResponse{}
|
||||
}
|
||||
|
||||
func (c *Client) sawPMPRecently() bool {
|
||||
@@ -375,7 +366,6 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor
|
||||
// find a PMP service, bail out early rather than probing
|
||||
// again. Cuts down latency for most clients.
|
||||
haveRecentPMP := c.sawPMPRecentlyLocked()
|
||||
|
||||
if haveRecentPMP {
|
||||
m.external = m.external.WithIP(c.pmpPubIP)
|
||||
}
|
||||
@@ -584,17 +574,17 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
// https://github.com/tailscale/tailscale/issues/1001
|
||||
if c.sawPMPRecently() {
|
||||
res.PMP = true
|
||||
} else if !DisablePMP {
|
||||
} else {
|
||||
uc.WriteTo(pmpReqExternalAddrPacket, pmpAddr)
|
||||
}
|
||||
if c.sawPCPRecently() {
|
||||
res.PCP = true
|
||||
} else if !DisablePCP {
|
||||
} else {
|
||||
uc.WriteTo(pcpAnnounceRequest(myIP), pcpAddr)
|
||||
}
|
||||
if c.sawUPnPRecently() {
|
||||
res.UPnP = true
|
||||
} else if !DisableUPnP {
|
||||
} else {
|
||||
uc.WriteTo(uPnPPacket, upnpAddr)
|
||||
}
|
||||
|
||||
@@ -620,9 +610,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
if err != nil {
|
||||
c.logf("unrecognized UPnP discovery response; ignoring")
|
||||
}
|
||||
if VerboseLogs {
|
||||
c.logf("UPnP reply %+v, %q", meta, buf[:n])
|
||||
}
|
||||
// log.Printf("UPnP reply %+v, %q", meta, buf[:n])
|
||||
res.UPnP = true
|
||||
c.mu.Lock()
|
||||
c.uPnPSawTime = time.Now()
|
||||
@@ -743,10 +731,9 @@ func parsePCPResponse(b []byte) (res pcpResponse, ok bool) {
|
||||
var pmpReqExternalAddrPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request"
|
||||
|
||||
const (
|
||||
upnpPort = 1900 // for UDP discovery only; TCP port discovered later
|
||||
upnpPort = 1900
|
||||
)
|
||||
|
||||
// uPnPPacket is the UPnP UDP discovery packet's request body.
|
||||
var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: 239.255.255.250:1900\r\n" +
|
||||
"ST: ssdp:all\r\n" +
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
@@ -23,9 +23,12 @@ import (
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// VerboseLogs controls verbose debug logging.
|
||||
// It exists for use by "tailscaled debug --portmap".
|
||||
var VerboseLogs bool
|
||||
|
||||
// References:
|
||||
//
|
||||
// WANIP Connection v2: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
|
||||
@@ -151,7 +154,7 @@ func addAnyPortMapping(
|
||||
//
|
||||
// The provided ctx is not retained in the returned upnpClient, but
|
||||
// its associated HTTP client is (if set via goupnp.WithHTTPClient).
|
||||
func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uPnPDiscoResponse) (client upnpClient, err error) {
|
||||
func getUPnPClient(ctx context.Context, gw netaddr.IP, meta uPnPDiscoResponse) (upnpClient, error) {
|
||||
if controlknobs.DisableUPnP() {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -161,7 +164,7 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uP
|
||||
}
|
||||
|
||||
if VerboseLogs {
|
||||
logf("fetching %v", meta.Location)
|
||||
log.Printf("fetching %v", meta.Location)
|
||||
}
|
||||
u, err := url.Parse(meta.Location)
|
||||
if err != nil {
|
||||
@@ -177,11 +180,7 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uP
|
||||
meta.Location, gw)
|
||||
}
|
||||
|
||||
// We're fetching a smallish XML document over plain HTTP
|
||||
// across the local LAN, without using DNS. There should be
|
||||
// very few round trips and low latency, so one second is a
|
||||
// long time.
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// This part does a network fetch.
|
||||
@@ -190,15 +189,6 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uP
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
logf("saw UPnP type %v at %v; %v (%v)",
|
||||
strings.TrimPrefix(fmt.Sprintf("%T", client), "*internetgateway2."),
|
||||
meta.Location, root.Device.FriendlyName, root.Device.Manufacturer)
|
||||
}()
|
||||
|
||||
// These parts don't do a network fetch.
|
||||
// Pick the best service type available.
|
||||
if cc, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(ctx, root, u); len(cc) > 0 {
|
||||
@@ -254,9 +244,9 @@ func (c *Client) getUPnPPortMapping(
|
||||
client = oldMapping.client
|
||||
} else {
|
||||
ctx := goupnp.WithHTTPClient(ctx, httpClient)
|
||||
client, err = getUPnPClient(ctx, c.logf, gw, meta)
|
||||
client, err = getUPnPClient(ctx, gw, meta)
|
||||
if VerboseLogs {
|
||||
c.logf("getUPnPClient: %T, %v", client, err)
|
||||
log.Printf("getUPnPClient: %T, %v", client, err)
|
||||
}
|
||||
if err != nil {
|
||||
return netaddr.IPPort{}, false
|
||||
@@ -276,7 +266,7 @@ func (c *Client) getUPnPPortMapping(
|
||||
time.Second*pmpMapLifetimeSec,
|
||||
)
|
||||
if VerboseLogs {
|
||||
c.logf("addAnyPortMapping: %v, %v", newPort, err)
|
||||
log.Printf("addAnyPortMapping: %v, %v", newPort, err)
|
||||
}
|
||||
if err != nil {
|
||||
return netaddr.IPPort{}, false
|
||||
@@ -284,7 +274,7 @@ func (c *Client) getUPnPPortMapping(
|
||||
// TODO cache this ip somewhere?
|
||||
extIP, err := client.GetExternalIPAddress(ctx)
|
||||
if VerboseLogs {
|
||||
c.logf("client.GetExternalIPAddress: %v, %v", extIP, err)
|
||||
log.Printf("client.GetExternalIPAddress: %v, %v", extIP, err)
|
||||
}
|
||||
if err != nil {
|
||||
// TODO this doesn't seem right
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
@@ -66,20 +64,9 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
name string
|
||||
xmlBody string
|
||||
want string
|
||||
wantLog string
|
||||
}{
|
||||
{
|
||||
"google",
|
||||
googleWifiRootDescXML,
|
||||
"*internetgateway2.WANIPConnection2",
|
||||
"saw UPnP type WANIPConnection2 at http://127.0.0.1:NNN/rootDesc.xml; OnHub (Google)\n",
|
||||
},
|
||||
{
|
||||
"pfsense",
|
||||
pfSenseRootDescXML,
|
||||
"*internetgateway2.WANIPConnection1",
|
||||
"saw UPnP type WANIPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; FreeBSD router (FreeBSD)\n",
|
||||
},
|
||||
{"google", googleWifiRootDescXML, "*internetgateway2.WANIPConnection2"},
|
||||
{"pfsense", pfSenseRootDescXML, "*internetgateway2.WANIPConnection1"},
|
||||
// TODO(bradfitz): find a PPP one in the wild
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -93,12 +80,7 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
gw, _ := netaddr.FromStdIP(ts.Listener.Addr().(*net.TCPAddr).IP)
|
||||
var logBuf bytes.Buffer
|
||||
logf := func(format string, a ...interface{}) {
|
||||
fmt.Fprintf(&logBuf, format, a...)
|
||||
logBuf.WriteByte('\n')
|
||||
}
|
||||
c, err := getUPnPClient(context.Background(), logf, gw, uPnPDiscoResponse{
|
||||
c, err := getUPnPClient(context.Background(), gw, uPnPDiscoResponse{
|
||||
Location: ts.URL + "/rootDesc.xml",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -108,10 +90,6 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
if got != tt.want {
|
||||
t.Errorf("got %v; want %v", got, tt.want)
|
||||
}
|
||||
gotLog := regexp.MustCompile(`127\.0\.0\.1:\d+`).ReplaceAllString(logBuf.String(), "127.0.0.1:NNN")
|
||||
if gotLog != tt.wantLog {
|
||||
t.Errorf("logged %q; want %q", gotLog, tt.wantLog)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"inet.af/netaddr"
|
||||
"inet.af/netstack/tcpip"
|
||||
"inet.af/netstack/tcpip/buffer"
|
||||
"inet.af/netstack/tcpip/header"
|
||||
"inet.af/netstack/tcpip/network/ipv4"
|
||||
"inet.af/netstack/tcpip/transport/udp"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
// TODO: this was randomly generated once. Maybe do it per process start? But
|
||||
// then an upgraded tailscaled would be visible to devices behind it. So
|
||||
// maybe instead make it a function of the tailscaled's wireguard public key?
|
||||
// For now just hard code it.
|
||||
var ourMAC = net.HardwareAddr{0x30, 0x2D, 0x66, 0xEC, 0x7A, 0x93}
|
||||
|
||||
func init() { createTAP = createTAPLinux }
|
||||
|
||||
func createTAPLinux(tapName, bridgeName string) (dev tun.Device, err error) {
|
||||
fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ifr struct {
|
||||
name [16]byte
|
||||
flags uint16
|
||||
_ [22]byte
|
||||
}
|
||||
copy(ifr.name[:], tapName)
|
||||
ifr.flags = syscall.IFF_TAP | syscall.IFF_NO_PI
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
|
||||
if errno != 0 {
|
||||
syscall.Close(fd)
|
||||
return nil, errno
|
||||
}
|
||||
if err = syscall.SetNonblock(fd, true); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := run("ip", "link", "set", "dev", tapName, "up"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bridgeName != "" {
|
||||
if err := run("brctl", "addif", bridgeName, tapName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dev, _, err = tun.CreateUnmonitoredTUNFromFD(fd) // TODO: MTU
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
type etherType [2]byte
|
||||
|
||||
var (
|
||||
etherTypeARP = etherType{0x08, 0x06}
|
||||
etherTypeIPv4 = etherType{0x08, 0x00}
|
||||
etherTypeIPv6 = etherType{0x86, 0xDD}
|
||||
)
|
||||
|
||||
const ipv4HeaderLen = 20
|
||||
|
||||
const (
|
||||
consumePacket = true
|
||||
passOnPacket = false
|
||||
)
|
||||
|
||||
// handleTAPFrame handles receiving a raw TAP ethernet frame and reports whether
|
||||
// it's been handled (that is, whether it should NOT be passed to wireguard).
|
||||
func (t *Wrapper) handleTAPFrame(ethBuf []byte) bool {
|
||||
|
||||
if len(ethBuf) < ethernetFrameSize {
|
||||
// Corrupt. Ignore.
|
||||
if tapDebug {
|
||||
t.logf("tap: short TAP frame")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
||||
_ = ethDstMAC
|
||||
et := etherType{ethBuf[12], ethBuf[13]}
|
||||
switch et {
|
||||
default:
|
||||
if tapDebug {
|
||||
t.logf("tap: ignoring etherType %v", et)
|
||||
}
|
||||
return consumePacket // filter out packet we should ignore
|
||||
case etherTypeIPv6:
|
||||
// TODO: support DHCPv6/ND/etc later. For now pass all to WireGuard.
|
||||
if tapDebug {
|
||||
t.logf("tap: ignoring IPv6 %v", et)
|
||||
}
|
||||
return passOnPacket
|
||||
case etherTypeIPv4:
|
||||
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen {
|
||||
// Bogus IPv4. Eat.
|
||||
if tapDebug {
|
||||
t.logf("tap: short ipv4")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
return t.handleDHCPRequest(ethBuf)
|
||||
case etherTypeARP:
|
||||
arpPacket := header.ARP(ethBuf[ethernetFrameSize:])
|
||||
if !arpPacket.IsValid() {
|
||||
// Bogus ARP. Eat.
|
||||
return consumePacket
|
||||
}
|
||||
switch arpPacket.Op() {
|
||||
case header.ARPRequest:
|
||||
req := arpPacket // better name at this point
|
||||
buf := make([]byte, header.EthernetMinimumSize+header.ARPSize)
|
||||
|
||||
// Our ARP "Table" of one:
|
||||
var srcMAC [6]byte
|
||||
copy(srcMAC[:], ethSrcMAC)
|
||||
if old := t.destMAC(); old != srcMAC {
|
||||
t.destMACAtomic.Store(srcMAC)
|
||||
}
|
||||
|
||||
eth := header.Ethernet(buf)
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: tcpip.LinkAddress(ourMAC[:]),
|
||||
DstAddr: tcpip.LinkAddress(ethSrcMAC),
|
||||
Type: 0x0806, // arp
|
||||
})
|
||||
res := header.ARP(buf[header.EthernetMinimumSize:])
|
||||
res.SetIPv4OverEthernet()
|
||||
res.SetOp(header.ARPReply)
|
||||
|
||||
// If the client's asking about their own IP, tell them it's
|
||||
// their own MAC. TODO(bradfitz): remove String allocs.
|
||||
if net.IP(req.ProtocolAddressTarget()).String() == theClientIP {
|
||||
copy(res.HardwareAddressSender(), ethSrcMAC)
|
||||
} else {
|
||||
copy(res.HardwareAddressSender(), ourMAC[:])
|
||||
}
|
||||
|
||||
copy(res.ProtocolAddressSender(), req.ProtocolAddressTarget())
|
||||
copy(res.HardwareAddressTarget(), req.HardwareAddressSender())
|
||||
copy(res.ProtocolAddressTarget(), req.ProtocolAddressSender())
|
||||
|
||||
n, err := t.tdev.Write(buf, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote ARP reply %v, %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
return consumePacket
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bradfitz): remove these hard-coded values and move from a /24 to a /10 CGNAT as the range.
|
||||
const theClientIP = "100.70.145.3" // TODO: make dynamic from netmap
|
||||
const routerIP = "100.70.145.1" // must be in same netmask (currently hack at /24) as theClientIP
|
||||
|
||||
// handleDHCPRequest handles receiving a raw TAP ethernet frame and reports whether
|
||||
// it's been handled as a DHCP request. That is, it reports whether the frame should
|
||||
// be ignored by the caller and not passed on.
|
||||
func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
|
||||
const udpHeader = 8
|
||||
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen+udpHeader {
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP short")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
||||
|
||||
if string(ethDstMAC) != "\xff\xff\xff\xff\xff\xff" {
|
||||
// Not a broadcast
|
||||
if tapDebug {
|
||||
t.logf("tap: dhcp no broadcast")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(ethBuf[ethernetFrameSize:])
|
||||
|
||||
if p.IPProto != ipproto.UDP || p.Src.Port() != 68 || p.Dst.Port() != 67 {
|
||||
// Not a DHCP request.
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP wrong meta")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
|
||||
dp, err := dhcpv4.FromBytes(ethBuf[ethernetFrameSize+ipv4HeaderLen+udpHeader:])
|
||||
if err != nil {
|
||||
// Bogus. Trash it.
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP FromBytes bad")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP request: %+v", dp)
|
||||
}
|
||||
switch dp.MessageType() {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
offer, err := dhcpv4.New(
|
||||
dhcpv4.WithReply(dp),
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
dhcpv4.WithRouter(net.ParseIP(routerIP)), // the default route
|
||||
dhcpv4.WithDNS(net.ParseIP("100.100.100.100")),
|
||||
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // TODO: what is this?
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
|
||||
dhcpv4.WithYourIP(net.ParseIP(theClientIP)),
|
||||
dhcpv4.WithLeaseTime(3600), // hour works
|
||||
//dhcpv4.WithHwAddr(ethSrcMAC),
|
||||
dhcpv4.WithNetmask(net.IPMask(net.ParseIP("255.255.255.0").To4())), // TODO: wrong
|
||||
//dhcpv4.WithTransactionID(dp.TransactionID),
|
||||
)
|
||||
if err != nil {
|
||||
t.logf("error building DHCP offer: %v", err)
|
||||
return consumePacket
|
||||
}
|
||||
// Make a layer 2 packet to write out:
|
||||
pkt := packLayer2UDP(
|
||||
offer.ToBytes(),
|
||||
ourMAC, ethSrcMAC,
|
||||
netaddr.IPPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
||||
netaddr.IPPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
||||
)
|
||||
n, err := t.tdev.Write(pkt, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote DHCP OFFER %v, %v", n, err)
|
||||
}
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
ack, err := dhcpv4.New(
|
||||
dhcpv4.WithReply(dp),
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeAck),
|
||||
dhcpv4.WithDNS(net.ParseIP("100.100.100.100")),
|
||||
dhcpv4.WithRouter(net.ParseIP(routerIP)), // the default route
|
||||
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // TODO: what is this?
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
|
||||
dhcpv4.WithYourIP(net.ParseIP(theClientIP)), // Hello world
|
||||
dhcpv4.WithLeaseTime(3600), // hour works
|
||||
dhcpv4.WithNetmask(net.IPMask(net.ParseIP("255.255.255.0").To4())),
|
||||
)
|
||||
if err != nil {
|
||||
t.logf("error building DHCP ack: %v", err)
|
||||
return consumePacket
|
||||
}
|
||||
// Make a layer 2 packet to write out:
|
||||
pkt := packLayer2UDP(
|
||||
ack.ToBytes(),
|
||||
ourMAC, ethSrcMAC,
|
||||
netaddr.IPPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
||||
netaddr.IPPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
||||
)
|
||||
n, err := t.tdev.Write(pkt, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote DHCP ACK %v, %v", n, err)
|
||||
}
|
||||
default:
|
||||
if tapDebug {
|
||||
t.logf("tap: unknown DHCP type")
|
||||
}
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
|
||||
func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst netaddr.IPPort) []byte {
|
||||
buf := buffer.NewView(header.EthernetMinimumSize + header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
|
||||
payloadStart := len(buf) - len(payload)
|
||||
copy(buf[payloadStart:], payload)
|
||||
srcB := src.IP().As4()
|
||||
srcIP := tcpip.Address(srcB[:])
|
||||
dstB := dst.IP().As4()
|
||||
dstIP := tcpip.Address(dstB[:])
|
||||
// Ethernet header
|
||||
eth := header.Ethernet(buf)
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: tcpip.LinkAddress(srcMAC),
|
||||
DstAddr: tcpip.LinkAddress(dstMAC),
|
||||
Type: ipv4.ProtocolNumber,
|
||||
})
|
||||
// IP header
|
||||
ipbuf := buf[header.EthernetMinimumSize:]
|
||||
ip := header.IPv4(ipbuf)
|
||||
ip.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(len(ipbuf)),
|
||||
TTL: 65,
|
||||
Protocol: uint8(udp.ProtocolNumber),
|
||||
SrcAddr: srcIP,
|
||||
DstAddr: dstIP,
|
||||
})
|
||||
ip.SetChecksum(^ip.CalculateChecksum())
|
||||
// UDP header
|
||||
u := header.UDP(buf[header.EthernetMinimumSize+header.IPv4MinimumSize:])
|
||||
u.Encode(&header.UDPFields{
|
||||
SrcPort: src.Port(),
|
||||
DstPort: dst.Port(),
|
||||
Length: uint16(header.UDPMinimumSize + len(payload)),
|
||||
})
|
||||
// Calculate the UDP pseudo-header checksum.
|
||||
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, srcIP, dstIP, uint16(len(u)))
|
||||
// Calculate the UDP checksum and set it.
|
||||
xsum = header.Checksum(payload, xsum)
|
||||
u.SetChecksum(^u.CalculateChecksum(xsum))
|
||||
return []byte(buf)
|
||||
}
|
||||
|
||||
func run(prog string, args ...string) error {
|
||||
cmd := exec.Command(prog, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error running %v: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Wrapper) destMAC() [6]byte {
|
||||
mac, _ := t.destMACAtomic.Load().([6]byte)
|
||||
return mac
|
||||
}
|
||||
|
||||
func (t *Wrapper) tapWrite(buf []byte, offset int) (int, error) {
|
||||
if offset < ethernetFrameSize {
|
||||
return 0, fmt.Errorf("[unexpected] weird offset %d for TAP write", offset)
|
||||
}
|
||||
eth := buf[offset-ethernetFrameSize:]
|
||||
dst := t.destMAC()
|
||||
copy(eth[:6], dst[:])
|
||||
copy(eth[6:12], ourMAC[:])
|
||||
et := etherTypeIPv4
|
||||
if buf[offset]>>4 == 6 {
|
||||
et = etherTypeIPv6
|
||||
}
|
||||
eth[12], eth[13] = et[0], et[1]
|
||||
if tapDebug {
|
||||
t.logf("tap: tapWrite off=%v % x", offset, buf)
|
||||
}
|
||||
return t.tdev.Write(buf, offset-ethernetFrameSize)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package tstun
|
||||
|
||||
func (*Wrapper) handleTAPFrame([]byte) bool { panic("unreachable") }
|
||||
func (*Wrapper) tapWrite([]byte, int) (int, error) { panic("unreachable") }
|
||||
@@ -8,12 +8,10 @@ package tstun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
@@ -37,32 +35,10 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// createTAP is non-nil on Linux.
|
||||
var createTAP func(tapName, bridgeName string) (tun.Device, error)
|
||||
|
||||
// New returns a tun.Device for the requested device name, along with
|
||||
// the OS-dependent name that was allocated to the device.
|
||||
func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
|
||||
var dev tun.Device
|
||||
var err error
|
||||
if strings.HasPrefix(tunName, "tap:") {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, "", errors.New("tap only works on Linux")
|
||||
}
|
||||
f := strings.Split(tunName, ":")
|
||||
var tapName, bridgeName string
|
||||
switch len(f) {
|
||||
case 2:
|
||||
tapName = f[1]
|
||||
case 3:
|
||||
tapName, bridgeName = f[1], f[2]
|
||||
default:
|
||||
return nil, "", errors.New("bogus tap argument")
|
||||
}
|
||||
dev, err = createTAP(tapName, bridgeName)
|
||||
} else {
|
||||
dev, err = tun.CreateTUN(tunName, tunMTU)
|
||||
}
|
||||
dev, err := tun.CreateTUN(tunName, tunMTU)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -8,10 +8,8 @@ package tstun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -23,7 +21,6 @@ import (
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/pad32"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
@@ -38,8 +35,6 @@ const PacketStartOffset = device.MessageTransportHeaderSize
|
||||
// of a packet that can be injected into a tstun.Wrapper.
|
||||
const MaxPacketSize = device.MaxContentSize
|
||||
|
||||
const tapDebug = false // for super verbose TAP debugging
|
||||
|
||||
var (
|
||||
// ErrClosed is returned when attempting an operation on a closed Wrapper.
|
||||
ErrClosed = errors.New("device closed")
|
||||
@@ -66,16 +61,13 @@ type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
|
||||
type Wrapper struct {
|
||||
logf logger.Logf
|
||||
// tdev is the underlying Wrapper device.
|
||||
tdev tun.Device
|
||||
isTAP bool // whether tdev is a TAP device
|
||||
tdev tun.Device
|
||||
|
||||
closeOnce sync.Once
|
||||
|
||||
_ pad32.Four
|
||||
lastActivityAtomic mono.Time // time of last send or receive
|
||||
|
||||
destIPActivity atomic.Value // of map[netaddr.IP]func()
|
||||
destMACAtomic atomic.Value // of [6]byte
|
||||
|
||||
// buffer stores the oldest unconsumed packet from tdev.
|
||||
// It is made a static buffer in order to avoid allocations.
|
||||
@@ -154,19 +146,10 @@ type tunReadResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func WrapTAP(logf logger.Logf, tdev tun.Device) *Wrapper {
|
||||
return wrap(logf, tdev, true)
|
||||
}
|
||||
|
||||
func Wrap(logf logger.Logf, tdev tun.Device) *Wrapper {
|
||||
return wrap(logf, tdev, false)
|
||||
}
|
||||
|
||||
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool) *Wrapper {
|
||||
tun := &Wrapper{
|
||||
logf: logger.WithPrefix(logf, "tstun: "),
|
||||
isTAP: isTAP,
|
||||
tdev: tdev,
|
||||
logf: logger.WithPrefix(logf, "tstun: "),
|
||||
tdev: tdev,
|
||||
// bufferConsumed is conceptually a condition variable:
|
||||
// a goroutine should not block when setting it, even with no listeners.
|
||||
bufferConsumed: make(chan struct{}, 1),
|
||||
@@ -301,14 +284,11 @@ func allowSendOnClosedChannel() {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
const ethernetFrameSize = 14 // 2 six byte MACs, 2 bytes ethertype
|
||||
|
||||
// poll polls t.tdev.Read, placing the oldest unconsumed packet into t.buffer.
|
||||
// This is needed because t.tdev.Read in general may block (it does on Windows),
|
||||
// so packets may be stuck in t.outbound if t.Read called t.tdev.Read directly.
|
||||
func (t *Wrapper) poll() {
|
||||
for range t.bufferConsumed {
|
||||
DoRead:
|
||||
var n int
|
||||
var err error
|
||||
// Read may use memory in t.buffer before PacketStartOffset for mandatory headers.
|
||||
@@ -323,33 +303,7 @@ func (t *Wrapper) poll() {
|
||||
if t.isClosed() {
|
||||
return
|
||||
}
|
||||
if t.isTAP {
|
||||
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset-ethernetFrameSize)
|
||||
if tapDebug {
|
||||
s := fmt.Sprintf("% x", t.buffer[:])
|
||||
for strings.HasSuffix(s, " 00") {
|
||||
s = strings.TrimSuffix(s, " 00")
|
||||
}
|
||||
t.logf("TAP read %v, %v: %s", n, err, s)
|
||||
}
|
||||
} else {
|
||||
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
|
||||
}
|
||||
}
|
||||
if t.isTAP {
|
||||
if err == nil {
|
||||
ethernetFrame := t.buffer[PacketStartOffset-ethernetFrameSize:][:n]
|
||||
if t.handleTAPFrame(ethernetFrame) {
|
||||
goto DoRead
|
||||
}
|
||||
}
|
||||
// Fall through. We got an IP packet.
|
||||
if n >= ethernetFrameSize {
|
||||
n -= ethernetFrameSize
|
||||
}
|
||||
if tapDebug {
|
||||
t.logf("tap regular frame: %x", t.buffer[PacketStartOffset:PacketStartOffset+n])
|
||||
}
|
||||
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
|
||||
}
|
||||
t.sendOutbound(tunReadResult{data: t.buffer[PacketStartOffset : PacketStartOffset+n], err: err})
|
||||
}
|
||||
@@ -567,13 +521,6 @@ func (t *Wrapper) Write(buf []byte, offset int) (int, error) {
|
||||
}
|
||||
|
||||
t.noteActivity()
|
||||
return t.tdevWrite(buf, offset)
|
||||
}
|
||||
|
||||
func (t *Wrapper) tdevWrite(buf []byte, offset int) (int, error) {
|
||||
if t.isTAP {
|
||||
return t.tapWrite(buf, offset)
|
||||
}
|
||||
return t.tdev.Write(buf, offset)
|
||||
}
|
||||
|
||||
@@ -606,7 +553,7 @@ func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
|
||||
}
|
||||
|
||||
// Write to the underlying device to skip filters.
|
||||
_, err := t.tdevWrite(buf, offset)
|
||||
_, err := t.tdev.Write(buf, offset)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -95,21 +95,16 @@ func (t Time) String() string {
|
||||
return fmt.Sprintf("mono.Time(ns=%d, estimated wall=%v)", int64(t), baseWall.Add(t.Sub(baseMono)).Truncate(0))
|
||||
}
|
||||
|
||||
// WallTime returns an approximate wall time that corresponded to t.
|
||||
func (t Time) WallTime() time.Time {
|
||||
if !t.IsZero() {
|
||||
return baseWall.Add(t.Sub(baseMono)).Truncate(0)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// MarshalJSON formats t for JSON as if it were a time.Time.
|
||||
// We format Time this way for backwards-compatibility.
|
||||
// This is best-effort only. Time does not survive a MarshalJSON/UnmarshalJSON round trip unchanged.
|
||||
// Since t is a monotonic time, it can vary from the actual wall clock by arbitrary amounts.
|
||||
// Even in the best of circumstances, it may vary by a few milliseconds.
|
||||
func (t Time) MarshalJSON() ([]byte, error) {
|
||||
tt := t.WallTime()
|
||||
var tt time.Time
|
||||
if !t.IsZero() {
|
||||
tt = baseWall.Add(t.Sub(baseMono)).Truncate(0)
|
||||
}
|
||||
return tt.MarshalJSON()
|
||||
}
|
||||
|
||||
@@ -121,10 +116,6 @@ func (t *Time) UnmarshalJSON(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tt.IsZero() {
|
||||
*t = 0
|
||||
return nil
|
||||
}
|
||||
*t = Now().Add(-time.Since(tt))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package mono
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -18,22 +17,6 @@ func TestNow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalZero(t *testing.T) {
|
||||
var tt time.Time
|
||||
buf, err := json.Marshal(tt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m Time
|
||||
err = json.Unmarshal(buf, &m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !m.IsZero() {
|
||||
t.Errorf("expected unmarshal of zero time to be 0, got %d (~=%v)", m, m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonoNow(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Now()
|
||||
|
||||
@@ -8,15 +8,8 @@
|
||||
// The hash is sufficiently strong and unique such that
|
||||
// Hash(x) == Hash(y) is an appropriate replacement for x == y.
|
||||
//
|
||||
// The definition of equality is identical to reflect.DeepEqual except:
|
||||
// * Floating-point values are compared based on the raw bits,
|
||||
// which means that NaNs (with the same bit pattern) are treated as equal.
|
||||
// * Types which implement interface { AppendTo([]byte) []byte } use
|
||||
// the AppendTo method to produce a textual representation of the value.
|
||||
// Thus, two values are equal if AppendTo produces the same bytes.
|
||||
//
|
||||
// WARNING: This package, like most of the tailscale.com Go module,
|
||||
// should be considered Tailscale-internal; we make no API promises.
|
||||
// This package, like most of the tailscale.com Go module, should be
|
||||
// considered Tailscale-internal; we make no API promises.
|
||||
package deephash
|
||||
|
||||
import (
|
||||
@@ -33,33 +26,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// There is much overlap between the theory of serialization and hashing.
|
||||
// A hash (useful for determing equality) can be produced by printing a value
|
||||
// and hashing the output. The format must:
|
||||
// * be deterministic such that the same value hashes to the same output, and
|
||||
// * be parsable such that the same value can be reproduced by the output.
|
||||
//
|
||||
// The logic below hashes a value by printing it to a hash.Hash.
|
||||
// To be parsable, it assumes that we know the Go type of each value:
|
||||
// * scalar types (e.g., bool or int32) are printed as fixed-width fields.
|
||||
// * list types (e.g., strings, slices, and AppendTo buffers) are prefixed
|
||||
// by a fixed-width length field, followed by the contents of the list.
|
||||
// * slices, arrays, and structs print each element/field consecutively.
|
||||
// * interfaces print with a 1-byte prefix indicating whether it is nil.
|
||||
// If non-nil, it is followed by a fixed-width field of the type index,
|
||||
// followed by the format of the underlying value.
|
||||
// * pointers print with a 1-byte prefix indicating whether the pointer is
|
||||
// 1) nil, 2) previously seen, or 3) newly seen. Previously seen pointers are
|
||||
// followed by a fixed-width field with the index of the previous pointer.
|
||||
// Newly seen pointers are followed by the format of the underlying value.
|
||||
// * maps print with a 1-byte prefix indicating whether the map pointer is
|
||||
// 1) nil, 2) previously seen, or 3) newly seen. Previously seen pointers
|
||||
// are followed by a fixed-width field of the index of the previous pointer.
|
||||
// Newly seen maps are printed as a fixed-width field with the XOR of the
|
||||
// hash of every map entry. With a sufficiently strong hash, this value is
|
||||
// theoretically "parsable" by looking up the hash in a magical map that
|
||||
// returns the set of entries for that given hash.
|
||||
|
||||
const scratchSize = 128
|
||||
|
||||
// hasher is reusable state for hashing a value.
|
||||
@@ -208,7 +174,10 @@ func (h *hasher) hashValue(v reflect.Value) {
|
||||
h.hashUint8(1) // indicates visiting a pointer
|
||||
h.hashValue(v.Elem())
|
||||
case reflect.Struct:
|
||||
w.WriteString("struct")
|
||||
h.hashUint64(uint64(v.NumField()))
|
||||
for i, n := 0, v.NumField(); i < n; i++ {
|
||||
h.hashUint64(uint64(i))
|
||||
h.hashValue(v.Field(i))
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
@@ -233,6 +202,7 @@ func (h *hasher) hashValue(v reflect.Value) {
|
||||
// TODO(dsnet): Perform cycle detection for slices,
|
||||
// which is functionally a list of pointers.
|
||||
// See https://github.com/google/go-cmp/blob/402949e8139bb890c71a707b6faf6dd05c92f4e5/cmp/compare.go#L438-L450
|
||||
h.hashUint64(uint64(i))
|
||||
h.hashValue(v.Index(i))
|
||||
}
|
||||
case reflect.Interface:
|
||||
|
||||
@@ -269,7 +269,11 @@ func TestPrintArray(t *testing.T) {
|
||||
h := &hasher{bw: bw}
|
||||
h.hashValue(reflect.ValueOf(x))
|
||||
bw.Flush()
|
||||
const want = "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f"
|
||||
const want = "struct" +
|
||||
"\x01\x00\x00\x00\x00\x00\x00\x00" + // 1 field
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" + // 0th field
|
||||
// the 32 bytes:
|
||||
"\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f"
|
||||
if got := got.Bytes(); string(got) != want {
|
||||
t.Errorf("wrong:\n got: %q\nwant: %q\n", got, want)
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
ps.LastWrite = as.lastSend.WallTime()
|
||||
ps.LastWrite = as.lastSend
|
||||
for i, ua := range as.ipPorts {
|
||||
if ua.IP() == derpMagicIPAddr {
|
||||
continue
|
||||
|
||||
@@ -3836,10 +3836,9 @@ func (de *discoEndpoint) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
return
|
||||
}
|
||||
|
||||
now := mono.Now()
|
||||
ps.LastWrite = de.lastSend.WallTime()
|
||||
ps.Active = now.Sub(de.lastSend) < sessionActiveTimeout
|
||||
ps.LastWrite = de.lastSend
|
||||
|
||||
now := mono.Now()
|
||||
if udpAddr, derpAddr := de.addrForSendLocked(now); !udpAddr.IsZero() && derpAddr.IsZero() {
|
||||
ps.CurAddr = udpAddr.String()
|
||||
}
|
||||
|
||||
@@ -149,10 +149,6 @@ type Config struct {
|
||||
// If nil, a fake Device that does nothing is used.
|
||||
Tun tun.Device
|
||||
|
||||
// IsTAP is whether Tun is actually a TAP (Layer 2) device that'll
|
||||
// require ethernet headers.
|
||||
IsTAP bool
|
||||
|
||||
// Router interfaces the Engine to the OS network stack.
|
||||
// If nil, a fake Router that does nothing is used.
|
||||
Router router.Router
|
||||
@@ -239,12 +235,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
conf.DNS = d
|
||||
}
|
||||
|
||||
var tsTUNDev *tstun.Wrapper
|
||||
if conf.IsTAP {
|
||||
tsTUNDev = tstun.WrapTAP(logf, conf.Tun)
|
||||
} else {
|
||||
tsTUNDev = tstun.Wrap(logf, conf.Tun)
|
||||
}
|
||||
tsTUNDev := tstun.Wrap(logf, conf.Tun)
|
||||
closePool.add(tsTUNDev)
|
||||
|
||||
e := &userspaceEngine{
|
||||
|
||||
Reference in New Issue
Block a user