mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-12 17:32:38 +00:00
net/portmapper: use the upstream goupnp library instead of our fork
Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: Iabf9fc4e158f45e4fc385f2b4c2e55ba01351c5c
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user