Copy goupnp client into our repo

goupnp is an existing upnp client for go, which provides all the functionality we need, licensed
under BSD-2-Clause, so we can copy it over and modify parts of it for our case.
Specifically, we add contexts to all the methods so we can better handle timeouts, remove the
dependency on large charsets, and (eventually) trim out extra components we don't need.

Signed-off-by: julianknodt <julianknodt@gmail.com>
This commit is contained in:
julianknodt
2021-06-14 13:06:37 -07:00
parent caceeff374
commit c6b92ddda8
32 changed files with 8825 additions and 103 deletions

View File

@@ -12,11 +12,13 @@ import (
"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.

View File

@@ -5,56 +5,26 @@ package portmapper
import (
"context"
"sync"
"time"
"github.com/huin/goupnp/dcps/internetgateway2"
"golang.org/x/sync/errgroup"
"inet.af/netaddr"
"tailscale.com/net/upnp/dcps/internetgateway2"
)
// 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
}()
type upnpMapping struct {
gw netaddr.IP
external netaddr.IPPort
internal netaddr.IPPort
useUntil time.Time
client upnpClient
}
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
}
func (u *upnpMapping) isCurrent() bool { return u.useUntil.After(time.Now()) }
func (u *upnpMapping) validUntil() time.Time { return u.useUntil }
func (u *upnpMapping) externalIPPort() netaddr.IPPort { return u.external }
func (u *upnpMapping) release() {
u.client.DeletePortMapping(context.Background(), "", u.external.Port(), "udp")
}
type upnpClient interface {
@@ -62,21 +32,61 @@ type upnpClient interface {
// 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,
ctx context.Context,
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)
DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) error
GetStatusInfo(ctx context.Context) (status string, lastErr string, uptime uint32, err error)
RequestTermination() error
RequestConnection() error
RequestTermination(ctx context.Context) error
RequestConnection(ctx context.Context) error
}
func AddAnyPortMapping(
ctx context.Context,
upnp upnpClient,
newRemoteHost string,
newExternalPort uint16,
newProtocol string,
newInternalPort uint16,
newInternalClient string,
newEnabled bool,
newPortMappingDescription string,
newLeaseDuration uint32,
) (newPort uint16, err error) {
if upnp, ok := upnp.(*internetgateway2.WANIPConnection2); ok {
return upnp.AddAnyPortMapping(
ctx,
newRemoteHost,
newExternalPort,
newProtocol,
newInternalPort,
newInternalClient,
newEnabled,
newPortMappingDescription,
newLeaseDuration,
)
}
err = upnp.AddPortMapping(
ctx,
newRemoteHost,
newExternalPort,
newProtocol,
newInternalPort,
newInternalClient,
newEnabled,
newPortMappingDescription,
newLeaseDuration,
)
return newInternalPort, err
}
// getUPnPClients gets a client for interfacing with UPnP, ignoring the underlying protocol for