diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 0baee818c..98cbd9add 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -22,6 +22,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/gorilla/csrf from tailscale.com/client/web github.com/gorilla/securecookie from github.com/gorilla/csrf github.com/hdevalence/ed25519consensus from tailscale.com/tka+ + 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/jsimonetti/rtnetlink from tailscale.com/net/interfaces+ L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink @@ -47,12 +53,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 github.com/tailscale/go-winio/internal/socket from github.com/tailscale/go-winio W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+ - 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 L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw github.com/tailscale/web-client-prebuilt from tailscale.com/client/web github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck @@ -250,7 +250,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep encoding/hex from crypto/x509+ encoding/json from expvar+ encoding/pem from crypto/tls+ - encoding/xml from github.com/tailscale/goupnp+ + encoding/xml from github.com/huin/goupnp+ errors from bufio+ expvar from tailscale.com/derp+ flag from github.com/peterbourgon/ff/v3+ @@ -293,7 +293,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep path from html/template+ path/filepath from crypto/x509+ reflect from crypto/x509+ - regexp from github.com/tailscale/goupnp/httpu+ + regexp from github.com/coreos/go-iptables/iptables+ regexp/syntax from regexp runtime/debug from tailscale.com/util/singleflight+ slices from tailscale.com/cmd/tailscale/cli+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index d7b43fa96..19d157511 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -98,6 +98,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/gorilla/csrf from tailscale.com/client/web github.com/gorilla/securecookie from github.com/gorilla/csrf github.com/hdevalence/ed25519consensus from tailscale.com/tka+ + 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/illarion/gonotify from tailscale.com/net/dns L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4 @@ -145,12 +151,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD 💣 github.com/tailscale/golang-x-crypto/internal/alias from github.com/tailscale/golang-x-crypto/chacha20 LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+ LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh - 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/tailscale/hujson from tailscale.com/ipn/conffile L 💣 github.com/tailscale/netlink from tailscale.com/wgengine/router+ github.com/tailscale/web-client-prebuilt from tailscale.com/client/web @@ -483,7 +483,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de encoding/hex from crypto/x509+ encoding/json from expvar+ encoding/pem from crypto/tls+ - encoding/xml from github.com/tailscale/goupnp+ + encoding/xml from github.com/aws/aws-sdk-go-v2/aws/protocol/xml+ errors from bufio+ expvar from tailscale.com/derp+ flag from net/http/httptest+ diff --git a/go.mod b/go.mod index 1108df7b9..97c563f90 100644 --- a/go.mod +++ b/go.mod @@ -108,6 +108,7 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/gorilla/securecookie v1.1.1 // indirect + github.com/huin/goupnp v1.3.0 // indirect ) require ( diff --git a/go.sum b/go.sum index fc025873f..9ba15d479 100644 --- a/go.sum +++ b/go.sum @@ -524,6 +524,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/net/portmapper/upnp.go b/net/portmapper/upnp.go index 31650a516..cb87c35eb 100644 --- a/net/portmapper/upnp.go +++ b/net/portmapper/upnp.go @@ -23,9 +23,9 @@ import ( "sync/atomic" "time" - "github.com/tailscale/goupnp" - "github.com/tailscale/goupnp/dcps/internetgateway2" - "github.com/tailscale/goupnp/soap" + "github.com/huin/goupnp" + "github.com/huin/goupnp/dcps/internetgateway2" + "github.com/huin/goupnp/soap" "tailscale.com/envknob" "tailscale.com/net/netns" "tailscale.com/types/logger" @@ -62,48 +62,27 @@ func (u *upnpMapping) GoodUntil() time.Time { return u.goodUntil } func (u *upnpMapping) RenewAfter() time.Time { return u.renewAfter } func (u *upnpMapping) External() netip.AddrPort { return u.external } func (u *upnpMapping) Release(ctx context.Context) { - u.client.DeletePortMapping(ctx, "", u.external.Port(), upnpProtocolUDP) + u.client.DeletePortMappingCtx(ctx, "", u.external.Port(), upnpProtocolUDP) } // upnpClient is an interface over the multiple different clients exported by goupnp, // exposing the functions we need for portmapping. Those clients are auto-generated from XML-specs, // which is why they're not very idiomatic. type upnpClient interface { - AddPortMapping( + AddPortMappingCtx( ctx context.Context, - - // remoteHost is the remote device sending packets to this device, in the format of x.x.x.x. - // The empty string, "", means any host out on the internet can send packets in. - remoteHost string, - - // externalPort is the exposed port of this port mapping. Visible during NAT operations. - // 0 will let the router select the port, but there is an additional call, - // `AddAnyPortMapping`, which is available on 1 of the 3 possible protocols, - // which should be used if available. See `addAnyPortMapping` below, which calls this if - // `AddAnyPortMapping` is not supported. - externalPort uint16, - - // protocol is whether this is over TCP or UDP. Either "TCP" or "UDP". - protocol string, - - // internalPort is the port that the gateway device forwards the traffic to. - internalPort uint16, - // internalClient is the IP address that packets will be forwarded to for this mapping. - // Internal client is of the form "x.x.x.x". - internalClient string, - - // enabled is whether this portmapping should be enabled or disabled. - enabled bool, - // portMappingDescription is a user-readable description of this portmapping. - portMappingDescription string, - // leaseDurationSec is the duration of this portmapping. The value of this argument must be - // greater than 0. From the spec, it appears if it is set to 0, it will switch to using - // 604800 seconds, but not sure why this is desired. The recommended time is 3600 seconds. - leaseDurationSec uint32, + NewRemoteHost string, + NewExternalPort uint16, + NewProtocol string, + NewInternalPort uint16, + NewInternalClient string, + NewEnabled bool, + NewPortMappingDescription string, + NewLeaseDuration uint32, ) error - DeletePortMapping(ctx context.Context, remoteHost string, externalPort uint16, protocol string) error - GetExternalIPAddress(ctx context.Context) (externalIPAddress string, err error) + DeletePortMappingCtx(ctx context.Context, remoteHost string, externalPort uint16, protocol string) error + GetExternalIPAddressCtx(ctx context.Context) (externalIPAddress string, err error) } // tsPortMappingDesc gets sent to UPnP clients as a human-readable label for the portmapping. @@ -153,7 +132,7 @@ func addAnyPortMapping( // First off, try using AddAnyPortMapping; if there's a conflict, the // router will pick another port and return it. if upnp, ok := upnp.(*internetgateway2.WANIPConnection2); ok { - return upnp.AddAnyPortMapping( + return upnp.AddAnyPortMappingCtx( ctx, "", externalPort, @@ -168,7 +147,7 @@ func addAnyPortMapping( // Fall back to using AddPortMapping, which requests a mapping to/from // a specific external port. - err = upnp.AddPortMapping( + err = upnp.AddPortMappingCtx( ctx, "", externalPort, @@ -182,8 +161,8 @@ func addAnyPortMapping( return externalPort, err } -// getUPnPClient gets a client for interfacing with UPnP, ignoring the underlying protocol for -// now. +// getUPnPClient gets a client for interfacing with UPnP, ignoring the +// underlying protocol for now. // Adapted from https://github.com/huin/goupnp/blob/master/GUIDE.md. // // The gw is the detected gateway. @@ -191,9 +170,11 @@ func addAnyPortMapping( // The meta is the most recently parsed UDP discovery packet response // from the Internet Gateway Device. // -// 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, debug DebugKnobs, gw netip.Addr, meta uPnPDiscoResponse) (client upnpClient, err error) { +// If set, the provided http.Client is copied and used as the HTTP client for +// SOAP requests. +// +// The provided ctx is not retained in the returned upnpClient. +func getUPnPClient(ctx context.Context, logf logger.Logf, httpc *http.Client, debug DebugKnobs, gw netip.Addr, meta uPnPDiscoResponse) (client upnpClient, err error) { if debug.DisableUPnP { return nil, nil } @@ -229,12 +210,16 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, debug DebugKnobs, gw n defer cancel() // This part does a network fetch. - root, err := goupnp.DeviceByURL(ctx, u) + root, err := goupnp.DeviceByURLCtx(ctx, u) if err != nil { return nil, err } + var soapClient *soap.SOAPClient defer func() { + if soapClient != nil && httpc != nil { + soapClient.HTTPClient = *httpc + } if client == nil { return } @@ -245,13 +230,16 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, debug DebugKnobs, gw n // These parts don't do a network fetch. // Pick the best service type available. - if cc, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(ctx, root, u); len(cc) > 0 { + if cc, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(root, u); len(cc) > 0 { + soapClient = cc[0].ServiceClient.SOAPClient return cc[0], nil } - if cc, _ := internetgateway2.NewWANIPConnection1ClientsFromRootDevice(ctx, root, u); len(cc) > 0 { + if cc, _ := internetgateway2.NewWANIPConnection1ClientsFromRootDevice(root, u); len(cc) > 0 { + soapClient = cc[0].ServiceClient.SOAPClient return cc[0], nil } - if cc, _ := internetgateway2.NewWANPPPConnection1ClientsFromRootDevice(ctx, root, u); len(cc) > 0 { + if cc, _ := internetgateway2.NewWANPPPConnection1ClientsFromRootDevice(root, u); len(cc) > 0 { + soapClient = cc[0].ServiceClient.SOAPClient return cc[0], nil } return nil, nil @@ -305,8 +293,7 @@ func (c *Client) getUPnPPortMapping( if ok && oldMapping != nil { client = oldMapping.client } else { - ctx := goupnp.WithHTTPClient(ctx, httpClient) - client, err = getUPnPClient(ctx, c.logf, c.debug, gw, meta) + client, err = getUPnPClient(ctx, c.logf, httpClient, c.debug, gw, meta) if c.debug.VerboseLogs { c.logf("getUPnPClient: %T, %v", client, err) } @@ -363,7 +350,7 @@ func (c *Client) getUPnPPortMapping( } // TODO cache this ip somewhere? - extIP, err := client.GetExternalIPAddress(ctx) + extIP, err := client.GetExternalIPAddressCtx(ctx) if c.debug.VerboseLogs { c.logf("client.GetExternalIPAddress: %v, %v", extIP, err) } diff --git a/net/portmapper/upnp_test.go b/net/portmapper/upnp_test.go index aac2ab95c..1e939f7f5 100644 --- a/net/portmapper/upnp_test.go +++ b/net/portmapper/upnp_test.go @@ -114,7 +114,7 @@ func TestGetUPnPClient(t *testing.T) { gw, _ := netip.AddrFromSlice(ts.Listener.Addr().(*net.TCPAddr).IP) gw = gw.Unmap() var logBuf tstest.MemLogger - c, err := getUPnPClient(context.Background(), logBuf.Logf, DebugKnobs{}, gw, uPnPDiscoResponse{ + c, err := getUPnPClient(context.Background(), logBuf.Logf, nil, DebugKnobs{}, gw, uPnPDiscoResponse{ Location: ts.URL + "/rootDesc.xml", }) if err != nil { diff --git a/tstest/jsdeps/jsdeps_test.go b/tstest/jsdeps/jsdeps_test.go index c3dbcd39a..5a2c52c26 100644 --- a/tstest/jsdeps/jsdeps_test.go +++ b/tstest/jsdeps/jsdeps_test.go @@ -14,11 +14,11 @@ func TestDeps(t *testing.T) { GOOS: "js", GOARCH: "wasm", BadDeps: map[string]string{ - "runtime/pprof": "bloat", - "golang.org/x/net/http2/h2c": "bloat", - "net/http/pprof": "bloat", - "golang.org/x/net/proxy": "bloat", - "github.com/tailscale/goupnp": "bloat, which can't work anyway in wasm", + "runtime/pprof": "bloat", + "golang.org/x/net/http2/h2c": "bloat", + "net/http/pprof": "bloat", + "golang.org/x/net/proxy": "bloat", + "github.com/huin/goupnp": "bloat, which can't work anyway in wasm", }, }.Check(t) }