133 lines
2.6 KiB
Go
133 lines
2.6 KiB
Go
package nat
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"time"
|
|
|
|
natpmp "github.com/jackpal/go-nat-pmp"
|
|
)
|
|
|
|
var (
|
|
_ NAT = (*natpmpNAT)(nil)
|
|
)
|
|
|
|
func discoverNATPMP(ctx context.Context) <-chan NAT {
|
|
res := make(chan NAT, 1)
|
|
|
|
ip, err := getDefaultGateway()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
go func() {
|
|
defer close(res)
|
|
// Unfortunately, we can't actually _stop_ the natpmp
|
|
// library. However, we can at least close _our_ channel
|
|
// and walk away.
|
|
select {
|
|
case client, ok := <-discoverNATPMPWithAddr(ip):
|
|
if ok {
|
|
res <- &natpmpNAT{client, ip, make(map[int]int)}
|
|
}
|
|
case <-ctx.Done():
|
|
}
|
|
}()
|
|
return res
|
|
}
|
|
|
|
func discoverNATPMPWithAddr(ip net.IP) <-chan *natpmp.Client {
|
|
res := make(chan *natpmp.Client, 1)
|
|
go func() {
|
|
defer close(res)
|
|
client := natpmp.NewClient(ip)
|
|
_, err := client.GetExternalAddress()
|
|
if err != nil {
|
|
return
|
|
}
|
|
res <- client
|
|
}()
|
|
return res
|
|
}
|
|
|
|
type natpmpNAT struct {
|
|
c *natpmp.Client
|
|
gateway net.IP
|
|
ports map[int]int
|
|
}
|
|
|
|
func (n *natpmpNAT) GetDeviceAddress() (addr net.IP, err error) {
|
|
return n.gateway, nil
|
|
}
|
|
|
|
func (n *natpmpNAT) GetInternalAddress() (addr net.IP, err error) {
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, iface := range ifaces {
|
|
addrs, err := iface.Addrs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
switch x := addr.(type) {
|
|
case *net.IPNet:
|
|
if x.Contains(n.gateway) {
|
|
return x.IP, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, ErrNoInternalAddress
|
|
}
|
|
|
|
func (n *natpmpNAT) GetExternalAddress() (addr net.IP, err error) {
|
|
res, err := n.c.GetExternalAddress()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d := res.ExternalIPAddress
|
|
return net.IPv4(d[0], d[1], d[2], d[3]), nil
|
|
}
|
|
|
|
func (n *natpmpNAT) AddPortMapping(_ context.Context, protocol string, internalPort int, _ string, timeout time.Duration) (int, error) {
|
|
var (
|
|
err error
|
|
)
|
|
|
|
timeoutInSeconds := int(timeout / time.Second)
|
|
|
|
if externalPort := n.ports[internalPort]; externalPort > 0 {
|
|
_, err = n.c.AddPortMapping(protocol, internalPort, externalPort, timeoutInSeconds)
|
|
if err == nil {
|
|
n.ports[internalPort] = externalPort
|
|
return externalPort, nil
|
|
}
|
|
}
|
|
|
|
for i := 0; i < 3; i++ {
|
|
externalPort := randomPort()
|
|
_, err = n.c.AddPortMapping(protocol, internalPort, externalPort, timeoutInSeconds)
|
|
if err == nil {
|
|
n.ports[internalPort] = externalPort
|
|
return externalPort, nil
|
|
}
|
|
}
|
|
|
|
return 0, err
|
|
}
|
|
|
|
func (n *natpmpNAT) DeletePortMapping(_ context.Context, _ string, internalPort int) (err error) {
|
|
delete(n.ports, internalPort)
|
|
return nil
|
|
}
|
|
|
|
func (n *natpmpNAT) Type() string {
|
|
return "NAT-PMP"
|
|
}
|