mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-22 21:08:38 +00:00
net/portmapper: add stateful prober
Previously, the prober was stateless, and probe needed to be called manually whenever additional probes were required. This adds a stateful prober, which can theoretically reuse clients between runs and have smarter strategies for delays between retries, which may be crucial depending on how slow UPnP is. Signed-off-by: julianknodt <julianknodt@gmail.com>
This commit is contained in:
parent
525eb5ce41
commit
caceeff374
@ -3,6 +3,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
||||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||||
|
github.com/huin/goupnp from github.com/huin/goupnp/dcps/internetgateway2
|
||||||
|
github.com/huin/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||||
|
github.com/huin/goupnp/httpu from github.com/huin/goupnp+
|
||||||
|
github.com/huin/goupnp/scpd from github.com/huin/goupnp
|
||||||
|
github.com/huin/goupnp/soap from github.com/huin/goupnp+
|
||||||
|
github.com/huin/goupnp/ssdp from github.com/huin/goupnp
|
||||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||||
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
|
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/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
|
||||||
@ -70,18 +76,32 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
golang.org/x/net/dns/dnsmessage from net
|
golang.org/x/net/dns/dnsmessage from net
|
||||||
|
golang.org/x/net/html from golang.org/x/net/html/charset
|
||||||
|
golang.org/x/net/html/atom from golang.org/x/net/html
|
||||||
|
golang.org/x/net/html/charset from github.com/huin/goupnp
|
||||||
golang.org/x/net/http/httpguts from net/http+
|
golang.org/x/net/http/httpguts from net/http+
|
||||||
golang.org/x/net/http/httpproxy from net/http
|
golang.org/x/net/http/httpproxy from net/http
|
||||||
golang.org/x/net/http2/hpack from net/http
|
golang.org/x/net/http2/hpack from net/http
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||||
D golang.org/x/net/route from net+
|
D golang.org/x/net/route from net+
|
||||||
golang.org/x/sync/errgroup from tailscale.com/derp
|
golang.org/x/sync/errgroup from tailscale.com/derp+
|
||||||
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
|
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
|
||||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||||
LD golang.org/x/sys/unix from tailscale.com/net/netns+
|
LD golang.org/x/sys/unix from tailscale.com/net/netns+
|
||||||
W golang.org/x/sys/windows from golang.org/x/sys/windows/registry+
|
W golang.org/x/sys/windows from golang.org/x/sys/windows/registry+
|
||||||
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg
|
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg
|
||||||
|
golang.org/x/text/encoding from golang.org/x/net/html/charset+
|
||||||
|
golang.org/x/text/encoding/charmap from golang.org/x/net/html/charset+
|
||||||
|
golang.org/x/text/encoding/htmlindex from golang.org/x/net/html/charset
|
||||||
|
golang.org/x/text/encoding/internal from golang.org/x/text/encoding/charmap+
|
||||||
|
golang.org/x/text/encoding/japanese from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/korean from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/simplifiedchinese from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/traditionalchinese from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/unicode from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/language from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/runes from golang.org/x/text/encoding/unicode
|
||||||
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
|
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
|
||||||
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
||||||
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
||||||
@ -126,7 +146,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
encoding/json from expvar+
|
encoding/json from expvar+
|
||||||
encoding/pem from crypto/tls+
|
encoding/pem from crypto/tls+
|
||||||
encoding/xml from tailscale.com/cmd/tailscale/cli
|
encoding/xml from tailscale.com/cmd/tailscale/cli+
|
||||||
errors from bufio+
|
errors from bufio+
|
||||||
expvar from tailscale.com/derp+
|
expvar from tailscale.com/derp+
|
||||||
flag from github.com/peterbourgon/ff/v2+
|
flag from github.com/peterbourgon/ff/v2+
|
||||||
|
@ -10,6 +10,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
|
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
|
||||||
github.com/golang/snappy from github.com/klauspost/compress/zstd
|
github.com/golang/snappy from github.com/klauspost/compress/zstd
|
||||||
github.com/google/btree from inet.af/netstack/tcpip/header+
|
github.com/google/btree from inet.af/netstack/tcpip/header+
|
||||||
|
github.com/huin/goupnp from github.com/huin/goupnp/dcps/internetgateway2
|
||||||
|
github.com/huin/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||||
|
github.com/huin/goupnp/httpu from github.com/huin/goupnp+
|
||||||
|
github.com/huin/goupnp/scpd from github.com/huin/goupnp
|
||||||
|
github.com/huin/goupnp/soap from github.com/huin/goupnp+
|
||||||
|
github.com/huin/goupnp/ssdp from github.com/huin/goupnp
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
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 from tailscale.com/wgengine/monitor
|
||||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
@ -170,6 +176,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
|
golang.org/x/net/html from golang.org/x/net/html/charset
|
||||||
|
golang.org/x/net/html/atom from golang.org/x/net/html
|
||||||
|
golang.org/x/net/html/charset from github.com/huin/goupnp
|
||||||
golang.org/x/net/http/httpguts from net/http+
|
golang.org/x/net/http/httpguts from net/http+
|
||||||
golang.org/x/net/http/httpproxy from net/http
|
golang.org/x/net/http/httpproxy from net/http
|
||||||
golang.org/x/net/http2/hpack from net/http
|
golang.org/x/net/http2/hpack from net/http
|
||||||
@ -178,7 +187,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/net/ipv6 from golang.zx2c4.com/wireguard/device+
|
golang.org/x/net/ipv6 from golang.zx2c4.com/wireguard/device+
|
||||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||||
D golang.org/x/net/route from net+
|
D golang.org/x/net/route from net+
|
||||||
golang.org/x/sync/errgroup from tailscale.com/derp
|
golang.org/x/sync/errgroup from tailscale.com/derp+
|
||||||
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
|
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
|
||||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||||
LD golang.org/x/sys/unix from github.com/mdlayher/netlink+
|
LD golang.org/x/sys/unix from github.com/mdlayher/netlink+
|
||||||
@ -187,6 +196,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled+
|
W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled+
|
||||||
W golang.org/x/sys/windows/svc/mgr from tailscale.com/cmd/tailscaled
|
W golang.org/x/sys/windows/svc/mgr from tailscale.com/cmd/tailscaled
|
||||||
golang.org/x/term from tailscale.com/logpolicy
|
golang.org/x/term from tailscale.com/logpolicy
|
||||||
|
golang.org/x/text/encoding from golang.org/x/net/html/charset+
|
||||||
|
golang.org/x/text/encoding/charmap from golang.org/x/net/html/charset+
|
||||||
|
golang.org/x/text/encoding/htmlindex from golang.org/x/net/html/charset
|
||||||
|
golang.org/x/text/encoding/internal from golang.org/x/text/encoding/charmap+
|
||||||
|
golang.org/x/text/encoding/japanese from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/korean from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/simplifiedchinese from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/traditionalchinese from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/encoding/unicode from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/language from golang.org/x/text/encoding/htmlindex
|
||||||
|
golang.org/x/text/runes from golang.org/x/text/encoding/unicode
|
||||||
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
|
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
|
||||||
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
||||||
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
||||||
@ -232,6 +252,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
encoding/json from expvar+
|
encoding/json from expvar+
|
||||||
encoding/pem from crypto/tls+
|
encoding/pem from crypto/tls+
|
||||||
|
encoding/xml from github.com/huin/goupnp+
|
||||||
errors from bufio+
|
errors from bufio+
|
||||||
expvar from tailscale.com/derp+
|
expvar from tailscale.com/derp+
|
||||||
flag from tailscale.com/cmd/tailscaled+
|
flag from tailscale.com/cmd/tailscaled+
|
||||||
|
3
go.mod
3
go.mod
@ -16,6 +16,7 @@ require (
|
|||||||
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
|
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
|
||||||
github.com/google/uuid v1.1.2
|
github.com/google/uuid v1.1.2
|
||||||
github.com/goreleaser/nfpm v1.10.3
|
github.com/goreleaser/nfpm v1.10.3
|
||||||
|
github.com/huin/goupnp v1.0.0 // indirect
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
|
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/klauspost/compress v1.12.2
|
github.com/klauspost/compress v1.12.2
|
||||||
@ -32,7 +33,7 @@ require (
|
|||||||
github.com/toqueteos/webbrowser v1.2.0
|
github.com/toqueteos/webbrowser v1.2.0
|
||||||
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
|
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
|
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139
|
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||||
|
6
go.sum
6
go.sum
@ -296,6 +296,9 @@ 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/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 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
|
||||||
|
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
|
||||||
|
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
|
||||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
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/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
@ -685,6 +688,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -718,6 +722,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
|
||||||
|
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go4.org/mem"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
@ -377,6 +376,12 @@ const (
|
|||||||
pmpCodeUnsupportedOpcode pmpResultCode = 5
|
pmpCodeUnsupportedOpcode pmpResultCode = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ProbeResult struct {
|
||||||
|
PCP bool
|
||||||
|
PMP bool
|
||||||
|
UPnP bool
|
||||||
|
}
|
||||||
|
|
||||||
func buildPMPRequestMappingPacket(localPort, prevPort uint16, lifetimeSec uint32) (pkt []byte) {
|
func buildPMPRequestMappingPacket(localPort, prevPort uint16, lifetimeSec uint32) (pkt []byte) {
|
||||||
pkt = make([]byte, 12)
|
pkt = make([]byte, 12)
|
||||||
|
|
||||||
@ -433,127 +438,6 @@ func parsePMPResponse(pkt []byte) (res pmpResponse, ok bool) {
|
|||||||
return res, true
|
return res, true
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProbeResult struct {
|
|
||||||
PCP bool
|
|
||||||
PMP bool
|
|
||||||
UPnP bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe returns a summary of which port mapping services are
|
|
||||||
// available on the network.
|
|
||||||
//
|
|
||||||
// If a probe has run recently and there haven't been any network changes since,
|
|
||||||
// the returned result might be server from the Client's cache, without
|
|
||||||
// sending any network traffic.
|
|
||||||
func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
|
||||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
|
||||||
if !ok {
|
|
||||||
return res, ErrGatewayNotFound
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err == nil {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.lastProbe = time.Now()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
uc, err := netns.Listener().ListenPacket(context.Background(), "udp4", ":0")
|
|
||||||
if err != nil {
|
|
||||||
c.logf("ProbePCP: %v", err)
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
defer uc.Close()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 250*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
defer closeCloserOnContextDone(ctx, uc)()
|
|
||||||
|
|
||||||
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
|
|
||||||
// https://github.com/tailscale/tailscale/issues/1001
|
|
||||||
if c.sawPMPRecently() {
|
|
||||||
res.PMP = true
|
|
||||||
} else {
|
|
||||||
uc.WriteTo(pmpReqExternalAddrPacket, pmpAddr)
|
|
||||||
}
|
|
||||||
if c.sawPCPRecently() {
|
|
||||||
res.PCP = true
|
|
||||||
} else {
|
|
||||||
uc.WriteTo(pcpAnnounceRequest(myIP), pcpAddr)
|
|
||||||
}
|
|
||||||
if c.sawUPnPRecently() {
|
|
||||||
res.UPnP = true
|
|
||||||
} else {
|
|
||||||
uc.WriteTo(uPnPPacket, upnpAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 1500)
|
|
||||||
pcpHeard := false // true when we get any PCP response
|
|
||||||
for {
|
|
||||||
if pcpHeard && res.PMP && res.UPnP {
|
|
||||||
// Nothing more to discover.
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
n, addr, err := uc.ReadFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
port := addr.(*net.UDPAddr).Port
|
|
||||||
switch port {
|
|
||||||
case upnpPort:
|
|
||||||
if mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) {
|
|
||||||
res.UPnP = true
|
|
||||||
c.mu.Lock()
|
|
||||||
c.uPnPSawTime = time.Now()
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
case pcpPort: // same as pmpPort
|
|
||||||
if pres, ok := parsePCPResponse(buf[:n]); ok {
|
|
||||||
if pres.OpCode == pcpOpReply|pcpOpAnnounce {
|
|
||||||
pcpHeard = true
|
|
||||||
c.mu.Lock()
|
|
||||||
c.pcpSawTime = time.Now()
|
|
||||||
c.mu.Unlock()
|
|
||||||
switch pres.ResultCode {
|
|
||||||
case pcpCodeOK:
|
|
||||||
c.logf("Got PCP response: epoch: %v", pres.Epoch)
|
|
||||||
res.PCP = true
|
|
||||||
continue
|
|
||||||
case pcpCodeNotAuthorized:
|
|
||||||
// A PCP service is running, but refuses to
|
|
||||||
// provide port mapping services.
|
|
||||||
res.PCP = false
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
// Fall through to unexpected log line.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.logf("unexpected PCP probe response: %+v", pres)
|
|
||||||
}
|
|
||||||
if pres, ok := parsePMPResponse(buf[:n]); ok {
|
|
||||||
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr && pres.ResultCode == pmpCodeOK {
|
|
||||||
c.logf("Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch)
|
|
||||||
res.PMP = true
|
|
||||||
c.mu.Lock()
|
|
||||||
c.pmpPubIP = pres.PublicAddr
|
|
||||||
c.pmpPubIPTime = time.Now()
|
|
||||||
c.pmpLastEpoch = pres.SecondsSinceEpoch
|
|
||||||
c.mu.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.logf("unexpected PMP probe response: %+v", pres)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
pcpVersion = 2
|
pcpVersion = 2
|
||||||
pcpPort = 5351
|
pcpPort = 5351
|
||||||
@ -627,14 +511,4 @@ func parsePCPResponse(b []byte) (res pcpResponse, ok bool) {
|
|||||||
return res, true
|
return res, true
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
upnpPort = 1900
|
|
||||||
)
|
|
||||||
|
|
||||||
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 pmpReqExternalAddrPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request"
|
var pmpReqExternalAddrPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request"
|
||||||
|
@ -32,12 +32,13 @@ func TestClientProbe(t *testing.T) {
|
|||||||
t.Skip("skipping test without HIT_NETWORK=1")
|
t.Skip("skipping test without HIT_NETWORK=1")
|
||||||
}
|
}
|
||||||
c := NewClient(t.Logf)
|
c := NewClient(t.Logf)
|
||||||
for i := 0; i < 2; i++ {
|
c.NewProber(context.Background())
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
res, err := c.Probe(context.Background())
|
res, err := c.Prober.CurrentStatus()
|
||||||
t.Logf("Got: %+v, %v", res, err)
|
t.Logf("Got(t=%dms): %+v, %v", i*100, res, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +48,8 @@ func TestClientProbeThenMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
c := NewClient(t.Logf)
|
c := NewClient(t.Logf)
|
||||||
c.SetLocalPort(1234)
|
c.SetLocalPort(1234)
|
||||||
res, err := c.Probe(context.Background())
|
c.NewProber(context.Background())
|
||||||
|
res, err := c.Prober.StatusBlock()
|
||||||
t.Logf("Probe: %+v, %v", res, err)
|
t.Logf("Probe: %+v, %v", res, err)
|
||||||
ext, err := c.CreateOrGetMapping(context.Background())
|
ext, err := c.CreateOrGetMapping(context.Background())
|
||||||
t.Logf("CreateOrGetMapping: %v, %v", ext, err)
|
t.Logf("CreateOrGetMapping: %v, %v", ext, err)
|
||||||
|
447
net/portmapper/probe.go
Normal file
447
net/portmapper/probe.go
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/net/netns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProbeResult struct {
|
||||||
|
PCP bool
|
||||||
|
PMP bool
|
||||||
|
UPnP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe returns a summary of which port mapping services are
|
||||||
|
// available on the network.
|
||||||
|
//
|
||||||
|
// If a probe has run recently and there haven't been any network changes since,
|
||||||
|
// the returned result might be served from the Client's cache, without
|
||||||
|
// sending any network traffic.
|
||||||
|
func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||||
|
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||||
|
if !ok {
|
||||||
|
return res, ErrGatewayNotFound
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.lastProbe = time.Now()
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
uc, err := netns.Listener().ListenPacket(ctx, "udp4", ":0")
|
||||||
|
if err != nil {
|
||||||
|
c.logf("ProbePCP: %v", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
defer uc.Close()
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 250*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
defer closeCloserOnContextDone(ctx, uc)()
|
||||||
|
|
||||||
|
pcpAddr := netaddr.IPPortFrom(gw, pcpPort).UDPAddr()
|
||||||
|
pmpAddr := netaddr.IPPortFrom(gw, pmpPort).UDPAddr()
|
||||||
|
|
||||||
|
// Don't send probes to services that we recently learned (for
|
||||||
|
// the same gw/myIP) are available. See
|
||||||
|
// https://github.com/tailscale/tailscale/issues/1001
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
defer wg.Wait()
|
||||||
|
if c.sawUPnPRecently() {
|
||||||
|
res.UPnP = true
|
||||||
|
} else {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
// TODO(jknodt) this is expensive, maybe it's worth caching it and just reusing it
|
||||||
|
// more aggressively
|
||||||
|
hasUPnP, _ := probeUPnP(ctx)
|
||||||
|
if hasUPnP {
|
||||||
|
res.UPnP = true
|
||||||
|
c.mu.Lock()
|
||||||
|
c.uPnPSawTime = time.Now()
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if c.sawPMPRecently() {
|
||||||
|
res.PMP = true
|
||||||
|
} else {
|
||||||
|
uc.WriteTo(pmpReqExternalAddrPacket, pmpAddr)
|
||||||
|
}
|
||||||
|
if c.sawPCPRecently() {
|
||||||
|
res.PCP = true
|
||||||
|
} else {
|
||||||
|
uc.WriteTo(pcpAnnounceRequest(myIP), pcpAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
pcpHeard := false // true when we get any PCP response
|
||||||
|
for {
|
||||||
|
if pcpHeard && res.PMP {
|
||||||
|
// Nothing more to discover.
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
n, _, err := uc.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
if pres, ok := parsePCPResponse(buf[:n]); ok {
|
||||||
|
if pres.OpCode == pcpOpReply|pcpOpAnnounce {
|
||||||
|
pcpHeard = true
|
||||||
|
c.mu.Lock()
|
||||||
|
c.pcpSawTime = time.Now()
|
||||||
|
c.mu.Unlock()
|
||||||
|
switch pres.ResultCode {
|
||||||
|
case pcpCodeOK:
|
||||||
|
c.logf("Got PCP response: epoch: %v", pres.Epoch)
|
||||||
|
res.PCP = true
|
||||||
|
continue
|
||||||
|
case pcpCodeNotAuthorized:
|
||||||
|
// A PCP service is running, but refuses to
|
||||||
|
// provide port mapping services.
|
||||||
|
res.PCP = false
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// Fall through to unexpected log line.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.logf("unexpected PCP probe response: %+v", pres)
|
||||||
|
}
|
||||||
|
if pres, ok := parsePMPResponse(buf[:n]); ok {
|
||||||
|
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr && pres.ResultCode == pmpCodeOK {
|
||||||
|
c.logf("Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch)
|
||||||
|
res.PMP = true
|
||||||
|
c.mu.Lock()
|
||||||
|
c.pmpPubIP = pres.PublicAddr
|
||||||
|
c.pmpPubIPTime = time.Now()
|
||||||
|
c.pmpLastEpoch = pres.SecondsSinceEpoch
|
||||||
|
c.mu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.logf("unexpected PMP probe response: %+v", pres)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Prober struct {
|
||||||
|
// pause signals the probe to either pause temporarily (true), or stop entirely (false)
|
||||||
|
// to restart the probe, send another pause to it.
|
||||||
|
pause chan<- bool
|
||||||
|
|
||||||
|
PMP *ProbeSubResult
|
||||||
|
PCP *ProbeSubResult
|
||||||
|
|
||||||
|
upnpClient upnpClient
|
||||||
|
UPnP *ProbeSubResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProber creates a new prober for a given client.
|
||||||
|
func (c *Client) NewProber(ctx context.Context) (p *Prober) {
|
||||||
|
stop := make(chan bool)
|
||||||
|
p = &Prober{
|
||||||
|
stop: stop,
|
||||||
|
|
||||||
|
PMP: NewProbeSubResult(),
|
||||||
|
PCP: NewProbeSubResult(),
|
||||||
|
UPnP: NewProbeSubResult(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
pmp_ctx, cancel := context.WithTimeout(ctx, 250*time.Millisecond)
|
||||||
|
hasPCP, hasPMP, err := c.probePMPAndPCP(pmp_ctx)
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
err = nil
|
||||||
|
// the global context has passed, exit cleanly
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pmp_ctx.Err() == context.DeadlineExceeded {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
p.PMP.Set(hasPMP, err)
|
||||||
|
p.PCP.Set(hasPCP, err)
|
||||||
|
|
||||||
|
t := time.NewTimer(trustServiceStillAvailableDuration * 3 / 4)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case should_pause := <-pause:
|
||||||
|
if !should_pause {
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
restart := <-pause
|
||||||
|
if !restart {
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-t.C: // break through and retry the connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Do not timeout on getting an initial client, as we can reuse it so paying an initial cost
|
||||||
|
// is fine.
|
||||||
|
upnpClient, err := getUPnPClient(ctx)
|
||||||
|
if upnpClient == nil || err != nil {
|
||||||
|
p.UPnP.Set(false, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.upnpClient = upnpClient
|
||||||
|
defer func() {
|
||||||
|
// unset client when no longer using it.
|
||||||
|
p.upnpClient = nil
|
||||||
|
upnpClient.RequestTermination()
|
||||||
|
}()
|
||||||
|
// TODO maybe do something fancy/dynamic with more delay (exponential back-off)
|
||||||
|
for {
|
||||||
|
upnp_ctx, cancel := context.WithTimeout(ctx, 6*time.Second)
|
||||||
|
retries := 0
|
||||||
|
hasUPnP := false
|
||||||
|
const num_connect_retries = 5
|
||||||
|
for retries < num_connect_retries {
|
||||||
|
status, _, _, statusErr := p.upnpClient.GetStatusInfo()
|
||||||
|
if statusErr != nil {
|
||||||
|
err = statusErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
hasUPnP = hasUPnP || status == "Connected"
|
||||||
|
if status == "Disconnected" {
|
||||||
|
upnpClient.RequestConnection()
|
||||||
|
}
|
||||||
|
retries += 1
|
||||||
|
}
|
||||||
|
// need to manually check these since GetStatusInfo doesn't take a context
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
err = nil
|
||||||
|
// the global context has passed, exit cleanly
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if upnp_ctx.Err() == context.DeadlineExceeded {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
p.UPnP.Set(hasUPnP, err)
|
||||||
|
|
||||||
|
t := time.NewTimer(trustServiceStillAvailableDuration * 3 / 4)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case should_pause := <-pause:
|
||||||
|
if !should_pause {
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
restart := <-pause
|
||||||
|
if !restart {
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-t.C: // break through and retry the connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gracefully turns the Prober off.
|
||||||
|
func (p *Prober) Stop() {
|
||||||
|
close(p.pause)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pauses the prober if currently running, or starts if it was previously paused
|
||||||
|
func (p *Prober) Toggle() {
|
||||||
|
p.pause <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentStatus returns the current results of the prober, regardless of whether they have
|
||||||
|
// completed or not.
|
||||||
|
func (p *Prober) CurrentStatus() (res ProbeResult, err error) {
|
||||||
|
hasPMP, errPMP := p.PMP.PresentCurrent()
|
||||||
|
res.PMP = hasPMP
|
||||||
|
err = errPMP
|
||||||
|
|
||||||
|
hasUPnP, errUPnP := p.UPnP.PresentCurrent()
|
||||||
|
res.UPnP = hasUPnP
|
||||||
|
if err == nil {
|
||||||
|
err = errUPnP
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPCP, errPCP := p.PCP.PresentCurrent()
|
||||||
|
res.PCP = hasPCP
|
||||||
|
if err == nil {
|
||||||
|
err = errPCP
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prober) StatusBlock() (res ProbeResult, err error) {
|
||||||
|
hasPMP, errPMP := p.PMP.PresentBlock()
|
||||||
|
res.PMP = hasPMP
|
||||||
|
err = errPMP
|
||||||
|
|
||||||
|
hasUPnP, errUPnP := p.UPnP.PresentBlock()
|
||||||
|
res.UPnP = hasUPnP
|
||||||
|
if err == nil {
|
||||||
|
err = errUPnP
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPCP, errPCP := p.PCP.PresentBlock()
|
||||||
|
res.PCP = hasPCP
|
||||||
|
if err == nil {
|
||||||
|
err = errPCP
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProbeSubResult struct {
|
||||||
|
cond *sync.Cond
|
||||||
|
// If this probe has finished, regardless of success or failure
|
||||||
|
completed bool
|
||||||
|
|
||||||
|
// whether or not this feature is present
|
||||||
|
present bool
|
||||||
|
// most recent error
|
||||||
|
err error
|
||||||
|
|
||||||
|
// time we last saw it to be available.
|
||||||
|
sawTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProbeSubResult() *ProbeSubResult {
|
||||||
|
return &ProbeSubResult{
|
||||||
|
cond: &sync.Cond{
|
||||||
|
L: &sync.Mutex{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresentBlock blocks until the probe completes, then returns the result.
|
||||||
|
func (psr *ProbeSubResult) PresentBlock() (bool, error) {
|
||||||
|
psr.cond.L.Lock()
|
||||||
|
defer psr.cond.L.Unlock()
|
||||||
|
for !psr.completed {
|
||||||
|
psr.cond.Wait()
|
||||||
|
}
|
||||||
|
return psr.present, psr.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresentCurrent returns the current state, regardless whether or not the probe has completed.
|
||||||
|
func (psr *ProbeSubResult) PresentCurrent() (bool, error) {
|
||||||
|
psr.cond.L.Lock()
|
||||||
|
defer psr.cond.L.Unlock()
|
||||||
|
present := psr.present && psr.sawTime.After(time.Now().Add(-trustServiceStillAvailableDuration))
|
||||||
|
return present, psr.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (psr *ProbeSubResult) Set(present bool, err error) {
|
||||||
|
saw := time.Now()
|
||||||
|
psr.cond.L.Lock()
|
||||||
|
psr.sawTime = saw
|
||||||
|
psr.completed = true
|
||||||
|
psr.err = err
|
||||||
|
psr.present = present
|
||||||
|
psr.cond.L.Unlock()
|
||||||
|
|
||||||
|
psr.cond.Broadcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) probePMPAndPCP(ctx context.Context) (pcp bool, pmp bool, err error) {
|
||||||
|
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||||
|
if !ok {
|
||||||
|
return false, false, ErrGatewayNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
uc, err := netns.Listener().ListenPacket(ctx, "udp4", ":0")
|
||||||
|
if err != nil {
|
||||||
|
c.logf("ProbePCP/PMP: %v", err)
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
defer uc.Close()
|
||||||
|
defer closeCloserOnContextDone(ctx, uc)()
|
||||||
|
|
||||||
|
pcpAddr := netaddr.IPPortFrom(gw, pcpPort).UDPAddr()
|
||||||
|
pmpAddr := netaddr.IPPortFrom(gw, pmpPort).UDPAddr()
|
||||||
|
|
||||||
|
// Don't send probes to services that we recently learned (for
|
||||||
|
// the same gw/myIP) are available. See
|
||||||
|
// https://github.com/tailscale/tailscale/issues/1001
|
||||||
|
if c.sawPMPRecently() {
|
||||||
|
pmp = true
|
||||||
|
} else {
|
||||||
|
uc.WriteTo(pmpReqExternalAddrPacket, pmpAddr)
|
||||||
|
}
|
||||||
|
if c.sawPCPRecently() {
|
||||||
|
pcp = true
|
||||||
|
} else {
|
||||||
|
uc.WriteTo(pcpAnnounceRequest(myIP), pcpAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
pcpHeard := false // true when we get any PCP response
|
||||||
|
for {
|
||||||
|
if pcpHeard && pmp {
|
||||||
|
// Nothing more to discover.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, _, err := uc.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return pcp, pmp, err
|
||||||
|
}
|
||||||
|
if pres, ok := parsePCPResponse(buf[:n]); ok {
|
||||||
|
if pres.OpCode == pcpOpReply|pcpOpAnnounce {
|
||||||
|
pcpHeard = true
|
||||||
|
c.mu.Lock()
|
||||||
|
c.pcpSawTime = time.Now()
|
||||||
|
c.mu.Unlock()
|
||||||
|
switch pres.ResultCode {
|
||||||
|
case pcpCodeOK:
|
||||||
|
c.logf("Got PCP response: epoch: %v", pres.Epoch)
|
||||||
|
pcp = true
|
||||||
|
continue
|
||||||
|
case pcpCodeNotAuthorized:
|
||||||
|
// A PCP service is running, but refuses to
|
||||||
|
// provide port mapping services.
|
||||||
|
pcp = false
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// Fall through to unexpected log line.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.logf("unexpected PCP probe response: %+v", pres)
|
||||||
|
}
|
||||||
|
if pres, ok := parsePMPResponse(buf[:n]); ok {
|
||||||
|
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr && pres.ResultCode == pmpCodeOK {
|
||||||
|
c.logf("Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch)
|
||||||
|
pmp = true
|
||||||
|
c.mu.Lock()
|
||||||
|
c.pmpPubIP = pres.PublicAddr
|
||||||
|
c.pmpPubIPTime = time.Now()
|
||||||
|
c.pmpLastEpoch = pres.SecondsSinceEpoch
|
||||||
|
c.mu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.logf("unexpected PMP probe response: %+v", pres)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
net/portmapper/probe_test.go
Normal file
26
net/portmapper/probe_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientProber(t *testing.T) {
|
||||||
|
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
|
||||||
|
t.Skip("skipping test without HIT_NETWORK=1")
|
||||||
|
}
|
||||||
|
c := NewClient(t.Logf)
|
||||||
|
ctx := context.Background()
|
||||||
|
prober := c.NewProber(ctx)
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
prober.Stop()
|
||||||
|
res, err := prober.CurrentStatus()
|
||||||
|
t.Logf("Got: %+v, %v", res, err)
|
||||||
|
}
|
120
net/portmapper/upnp.go
Normal file
120
net/portmapper/upnp.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/huin/goupnp/dcps/internetgateway2"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// probeUPnP returns true if there are any upnp clients, or false with an error if none can be
|
||||||
|
// found.
|
||||||
|
func probeUPnP(ctx context.Context) (bool, error) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
any := make(chan bool)
|
||||||
|
errChan := make(chan error)
|
||||||
|
wg.Add(3)
|
||||||
|
go func() {
|
||||||
|
ip1Clients, _, err := internetgateway2.NewWANIPConnection1Clients()
|
||||||
|
if len(ip1Clients) > 0 {
|
||||||
|
any <- true
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
wg.Wait()
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
ip2Clients, _, err := internetgateway2.NewWANIPConnection2Clients()
|
||||||
|
if len(ip2Clients) > 0 {
|
||||||
|
any <- true
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
wg.Wait()
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
ppp1Clients, _, err := internetgateway2.NewWANPPPConnection1Clients()
|
||||||
|
if len(ppp1Clients) > 0 {
|
||||||
|
any <- true
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
wg.Wait()
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-any:
|
||||||
|
return true, nil
|
||||||
|
case err := <-errChan:
|
||||||
|
// TODO probably want to take the non-nil of all the errors? Or something.
|
||||||
|
return false, err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type upnpClient interface {
|
||||||
|
// http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
|
||||||
|
// Implicitly assume that the calls for all these are uniform, which might be a dangerous
|
||||||
|
// assumption.
|
||||||
|
AddPortMapping(
|
||||||
|
NewRemoteHost string,
|
||||||
|
NewExternalPort uint16,
|
||||||
|
NewProtocol string,
|
||||||
|
NewInternalPort uint16,
|
||||||
|
NewInternalClient string,
|
||||||
|
NewEnabled bool,
|
||||||
|
NewPortMappingDescription string,
|
||||||
|
NewLeaseDuration uint32,
|
||||||
|
) (err error)
|
||||||
|
|
||||||
|
DeletePortMapping(NewRemoteHost string, NewExternalPort uint16, NewProtocol string) error
|
||||||
|
GetStatusInfo() (status string, lastErr string, uptime uint32, err error)
|
||||||
|
|
||||||
|
RequestTermination() error
|
||||||
|
RequestConnection() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUPnPClients gets a client for interfacing with UPnP, ignoring the underlying protocol for
|
||||||
|
// now.
|
||||||
|
// Adapted from https://github.com/huin/goupnp/blob/master/GUIDE.md.
|
||||||
|
func getUPnPClient(ctx context.Context) (upnpClient, error) {
|
||||||
|
tasks, _ := errgroup.WithContext(ctx)
|
||||||
|
var ip1Clients []*internetgateway2.WANIPConnection1
|
||||||
|
tasks.Go(func() error {
|
||||||
|
var err error
|
||||||
|
ip1Clients, _, err = internetgateway2.NewWANIPConnection1Clients()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
var ip2Clients []*internetgateway2.WANIPConnection2
|
||||||
|
tasks.Go(func() error {
|
||||||
|
var err error
|
||||||
|
ip2Clients, _, err = internetgateway2.NewWANIPConnection2Clients()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
var ppp1Clients []*internetgateway2.WANPPPConnection1
|
||||||
|
tasks.Go(func() error {
|
||||||
|
var err error
|
||||||
|
ppp1Clients, _, err = internetgateway2.NewWANPPPConnection1Clients()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
err := tasks.Wait()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(ip2Clients) > 0:
|
||||||
|
return ip2Clients[0], nil
|
||||||
|
case len(ip1Clients) > 0:
|
||||||
|
return ip1Clients[0], nil
|
||||||
|
case len(ppp1Clients) > 0:
|
||||||
|
return ppp1Clients[0], nil
|
||||||
|
default:
|
||||||
|
// Didn't get any outputs, report if there was an error or nil if
|
||||||
|
// just no clients.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user