net/portmapper, go.mod: unfork our goupnp dependency
Updates #7436 Signed-off-by: Andrew Dunham <andrew@tailscale.com>
This commit is contained in:
committed by
Andrew Dunham
parent
5019dc8eb2
commit
6aac87a84c
@@ -10,8 +10,8 @@ package portmapper
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
"github.com/tailscale/goupnp/soap"
|
||||
"github.com/huin/goupnp"
|
||||
"github.com/huin/goupnp/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,8 +32,8 @@ type legacyWANPPPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) AddPortMapping(
|
||||
// AddPortMappingCtx implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) AddPortMappingCtx(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
@@ -85,11 +85,11 @@ func (client *legacyWANPPPConnection1) AddPortMapping(
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "AddPortMapping", request, response)
|
||||
return client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANPPPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// DeletePortMappingCtx implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) DeletePortMappingCtx(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
@@ -110,11 +110,11 @@ func (client *legacyWANPPPConnection1) DeletePortMapping(ctx context.Context, Ne
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "DeletePortMapping", request, response)
|
||||
return client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANPPPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// GetExternalIPAddressCtx implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetExternalIPAddressCtx(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
@@ -124,7 +124,7 @@ func (client *legacyWANPPPConnection1) GetExternalIPAddress(ctx context.Context)
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
if err = client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANPPPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ func (client *legacyWANPPPConnection1) GetExternalIPAddress(ctx context.Context)
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// GetStatusInfoCtx implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetStatusInfoCtx(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
@@ -147,7 +147,7 @@ func (client *legacyWANPPPConnection1) GetStatusInfo(ctx context.Context) (NewCo
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
if err = client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANPPPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,8 +171,8 @@ type legacyWANIPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) AddPortMapping(
|
||||
// AddPortMappingCtx implements upnpClient
|
||||
func (client *legacyWANIPConnection1) AddPortMappingCtx(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
@@ -224,11 +224,11 @@ func (client *legacyWANIPConnection1) AddPortMapping(
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "AddPortMapping", request, response)
|
||||
return client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANIPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// DeletePortMappingCtx implements upnpClient
|
||||
func (client *legacyWANIPConnection1) DeletePortMappingCtx(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
@@ -249,11 +249,11 @@ func (client *legacyWANIPConnection1) DeletePortMapping(ctx context.Context, New
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "DeletePortMapping", request, response)
|
||||
return client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANIPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// GetExternalIPAddressCtx implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetExternalIPAddressCtx(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
@@ -263,7 +263,7 @@ func (client *legacyWANIPConnection1) GetExternalIPAddress(ctx context.Context)
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
if err = client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANIPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,8 +273,8 @@ func (client *legacyWANIPConnection1) GetExternalIPAddress(ctx context.Context)
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// GetStatusInfoCtx implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetStatusInfoCtx(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
@@ -286,7 +286,7 @@ func (client *legacyWANIPConnection1) GetStatusInfo(ctx context.Context) (NewCon
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
if err = client.SOAPClient.PerformActionCtx(ctx, urn_LegacyWANIPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
"github.com/tailscale/goupnp/dcps/internetgateway2"
|
||||
"github.com/huin/goupnp"
|
||||
"github.com/huin/goupnp/dcps/internetgateway2"
|
||||
)
|
||||
|
||||
// NOTE: this is in a distinct file because the various string constants are
|
||||
@@ -168,7 +168,7 @@ func TestSelectBestService(t *testing.T) {
|
||||
|
||||
// Ensure that we're using the HTTP client that talks to our test IGD server
|
||||
ctx := context.Background()
|
||||
ctx = goupnp.WithHTTPClient(ctx, c.upnpHTTPClientLocked())
|
||||
ctx = upnpHTTPClientKey.WithValue(ctx, c.upnpHTTPClientLocked())
|
||||
|
||||
loc := mustParseURL(igd.ts.URL)
|
||||
rootDev := mustParseRootDev(t, rootDesc, loc)
|
||||
|
||||
+56
-22
@@ -25,15 +25,47 @@ 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"
|
||||
"tailscale.com/util/ctxkey"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
// upnpHTTPClientKey is a context key for storing an HTTP client to use
|
||||
// for UPnP requests. This allows us to use a custom HTTP client (with custom
|
||||
// dialer, timeouts, etc.) while using the upstream goupnp library which only
|
||||
// supports a global HTTPClientDefault.
|
||||
var upnpHTTPClientKey = ctxkey.New[*http.Client]("portmapper.upnpHTTPClient", nil)
|
||||
|
||||
// delegatingRoundTripper implements http.RoundTripper by delegating to
|
||||
// the HTTP client stored in the request's context. This allows us to use
|
||||
// per-request HTTP client configuration with the upstream goupnp library.
|
||||
type delegatingRoundTripper struct {
|
||||
inner *http.Client
|
||||
}
|
||||
|
||||
func (d delegatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if c := upnpHTTPClientKey.Value(req.Context()); c != nil {
|
||||
return c.Transport.RoundTrip(req)
|
||||
}
|
||||
return d.inner.Do(req)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The upstream goupnp library uses a global HTTP client for all
|
||||
// requests, while we want to be able to use a per-Client
|
||||
// [http.Client]. We replace its global HTTP client with one that
|
||||
// delegates to the HTTP client stored in the request's context.
|
||||
old := goupnp.HTTPClientDefault
|
||||
goupnp.HTTPClientDefault = &http.Client{
|
||||
Transport: delegatingRoundTripper{old},
|
||||
}
|
||||
}
|
||||
|
||||
// References:
|
||||
//
|
||||
// WANIP Connection v2: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
|
||||
@@ -79,14 +111,17 @@ func (u *upnpMapping) MappingDebug() string {
|
||||
u.loc)
|
||||
}
|
||||
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.
|
||||
//
|
||||
// The method names use the *Ctx suffix to match the upstream goupnp library's convention
|
||||
// for context-aware methods.
|
||||
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.
|
||||
@@ -119,9 +154,9 @@ type upnpClient interface {
|
||||
leaseDurationSec uint32,
|
||||
) error
|
||||
|
||||
DeletePortMapping(ctx context.Context, remoteHost string, externalPort uint16, protocol string) error
|
||||
GetExternalIPAddress(ctx context.Context) (externalIPAddress string, err error)
|
||||
GetStatusInfo(ctx context.Context) (status string, lastConnError string, uptime uint32, err error)
|
||||
DeletePortMappingCtx(ctx context.Context, remoteHost string, externalPort uint16, protocol string) error
|
||||
GetExternalIPAddressCtx(ctx context.Context) (externalIPAddress string, err error)
|
||||
GetStatusInfoCtx(ctx context.Context) (status string, lastConnError string, uptime uint32, err error)
|
||||
}
|
||||
|
||||
// tsPortMappingDesc gets sent to UPnP clients as a human-readable label for the portmapping.
|
||||
@@ -171,7 +206,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,
|
||||
@@ -186,7 +221,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,
|
||||
@@ -244,7 +279,7 @@ func getUPnPRootDevice(ctx context.Context, logf logger.Logf, debug DebugKnobs,
|
||||
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, nil, err
|
||||
}
|
||||
@@ -257,8 +292,7 @@ func getUPnPRootDevice(ctx context.Context, logf logger.Logf, debug DebugKnobs,
|
||||
//
|
||||
// loc is the parsed location that was used to fetch the given RootDevice.
|
||||
//
|
||||
// The provided ctx is not retained in the returned upnpClient, but
|
||||
// its associated HTTP client is (if set via goupnp.WithHTTPClient).
|
||||
// The provided ctx is not retained in the returned upnpClient.
|
||||
func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootDevice, loc *url.URL) (client upnpClient, err error) {
|
||||
method := "none"
|
||||
defer func() {
|
||||
@@ -274,9 +308,9 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
||||
// First, get all available clients from the device, and append to our
|
||||
// list of possible clients. Order matters here; we want to prefer
|
||||
// WANIPConnection2 over WANIPConnection1 or WANPPPConnection.
|
||||
wanIP2, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(ctx, root, loc)
|
||||
wanIP1, _ := internetgateway2.NewWANIPConnection1ClientsFromRootDevice(ctx, root, loc)
|
||||
wanPPP, _ := internetgateway2.NewWANPPPConnection1ClientsFromRootDevice(ctx, root, loc)
|
||||
wanIP2, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(root, loc)
|
||||
wanIP1, _ := internetgateway2.NewWANIPConnection1ClientsFromRootDevice(root, loc)
|
||||
wanPPP, _ := internetgateway2.NewWANPPPConnection1ClientsFromRootDevice(root, loc)
|
||||
|
||||
var clients []upnpClient
|
||||
for _, v := range wanIP2 {
|
||||
@@ -291,12 +325,12 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
||||
|
||||
// These are legacy services that were deprecated in 2015, but are
|
||||
// still in use by older devices; try them just in case.
|
||||
legacyClients, _ := goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANPPPConnection_1)
|
||||
legacyClients, _ := goupnp.NewServiceClientsFromRootDevice(root, loc, urn_LegacyWANPPPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANPPPConnection1{client})
|
||||
}
|
||||
legacyClients, _ = goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANIPConnection_1)
|
||||
legacyClients, _ = goupnp.NewServiceClientsFromRootDevice(root, loc, urn_LegacyWANIPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANIPConnection1{client})
|
||||
@@ -346,7 +380,7 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
||||
}
|
||||
|
||||
// Check if the device has an external IP address.
|
||||
extIP, err := svc.GetExternalIPAddress(ctx)
|
||||
extIP, err := svc.GetExternalIPAddressCtx(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -399,7 +433,7 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
||||
// serviceIsConnected returns whether a given UPnP service is connected, based
|
||||
// on the NewConnectionStatus field returned from GetStatusInfo.
|
||||
func serviceIsConnected(ctx context.Context, logf logger.Logf, svc upnpClient) bool {
|
||||
status, _ /* NewLastConnectionError */, _ /* NewUptime */, err := svc.GetStatusInfo(ctx)
|
||||
status, _ /* NewLastConnectionError */, _ /* NewUptime */, err := svc.GetStatusInfoCtx(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -454,7 +488,7 @@ func (c *Client) getUPnPPortMapping(
|
||||
c.mu.Lock()
|
||||
oldMapping, ok := c.mapping.(*upnpMapping)
|
||||
metas := c.uPnPMetas
|
||||
ctx = goupnp.WithHTTPClient(ctx, c.upnpHTTPClientLocked())
|
||||
ctx = upnpHTTPClientKey.WithValue(ctx, c.upnpHTTPClientLocked())
|
||||
c.mu.Unlock()
|
||||
|
||||
// Wrapper for a uPnPDiscoResponse with an optional existing root
|
||||
@@ -629,7 +663,7 @@ func (c *Client) tryUPnPPortmapWithDevice(
|
||||
}
|
||||
|
||||
// TODO cache this ip somewhere?
|
||||
extIP, err := client.GetExternalIPAddress(ctx)
|
||||
extIP, err := client.GetExternalIPAddressCtx(ctx)
|
||||
c.vlogf("client.GetExternalIPAddress: %v, %v", extIP, err)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, nil, err
|
||||
|
||||
Reference in New Issue
Block a user