Commit 943d7b39 authored by Steven Allen's avatar Steven Allen

expose all discovered NATs

This change makes it possible to configure all discovered NATs, not just the
first one found.
parent 51722233
......@@ -2,6 +2,7 @@
package nat
import (
"context"
"errors"
"math"
"math/rand"
......@@ -34,20 +35,64 @@ type NAT interface {
DeletePortMapping(protocol string, internalPort int) (err error)
}
// DiscoverNATs returns all NATs discovered in the network.
func DiscoverNATs(ctx context.Context) <-chan NAT {
nats := make(chan NAT)
go func() {
defer close(nats)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
upnpIg1 := discoverUPNP_IG1(ctx)
upnpIg2 := discoverUPNP_IG2(ctx)
natpmp := discoverNATPMP(ctx)
upnpGenIGDev := discoverUPNP_GenIGDev(ctx)
for upnpIg1 != nil || upnpIg2 != nil || natpmp != nil {
var (
nat NAT
ok bool
)
select {
case nat, ok = <-upnpIg1:
if !ok {
upnpIg1 = nil
}
case nat, ok = <-upnpIg2:
if !ok {
upnpIg2 = nil
}
case nat, ok = <-upnpGenIGDev:
if !ok {
upnpGenIGDev = nil
}
case nat, ok = <-natpmp:
if !ok {
natpmp = nil
}
}
if ok {
select {
case nats <- nat:
case <-ctx.Done():
return
}
}
}
}()
return nats
}
// DiscoverGateway attempts to find a gateway device.
func DiscoverGateway() (NAT, error) {
select {
case nat := <-discoverUPNP_IG1():
return nat, nil
case nat := <-discoverUPNP_IG2():
return nat, nil
case nat := <-discoverUPNP_GenIGDev():
return nat, nil
case nat := <-discoverNATPMP():
return nat, nil
case <-time.After(10 * time.Second):
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
nat := <-DiscoverNATs(ctx)
if nat == nil {
return nil, ErrNoNATFound
}
return nat, nil
}
func randomPort() int {
......
package nat
import (
"context"
"net"
"time"
......@@ -12,25 +13,42 @@ var (
_ NAT = (*natpmpNAT)(nil)
)
func discoverNATPMP() <-chan NAT {
func discoverNATPMP(ctx context.Context) <-chan NAT {
res := make(chan NAT, 1)
ip, err := gateway.DiscoverGateway()
if err == nil {
go discoverNATPMPWithAddr(res, ip)
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():
}
}()
} else {
close(res)
}
return res
}
func discoverNATPMPWithAddr(c chan NAT, ip net.IP) {
client := natpmp.NewClient(ip)
_, err := client.GetExternalAddress()
if err != nil {
return
}
c <- &natpmpNAT{client, ip, make(map[int]int)}
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 {
......
package nat
import (
"context"
"net"
"net/url"
"strings"
......@@ -17,9 +18,10 @@ var (
_ NAT = (*upnp_NAT)(nil)
)
func discoverUPNP_IG1() <-chan NAT {
res := make(chan NAT, 1)
func discoverUPNP_IG1(ctx context.Context) <-chan NAT {
res := make(chan NAT)
go func() {
defer close(res)
// find devices
devs, err := goupnp.DiscoverDevices(internetgateway1.URN_WANConnectionDevice_1)
......@@ -33,6 +35,9 @@ func discoverUPNP_IG1() <-chan NAT {
}
dev.Root.Device.VisitServices(func(srv *goupnp.Service) {
if ctx.Err() != nil {
return
}
switch srv.ServiceType {
case internetgateway1.URN_WANIPConnection_1:
client := &internetgateway1.WANIPConnection1{ServiceClient: goupnp.ServiceClient{
......@@ -42,8 +47,10 @@ func discoverUPNP_IG1() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-IP1)", dev.Root}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-IP1)", dev.Root}:
case <-ctx.Done():
}
}
case internetgateway1.URN_WANPPPConnection_1:
......@@ -54,8 +61,10 @@ func discoverUPNP_IG1() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-PPP1)", dev.Root}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-PPP1)", dev.Root}:
case <-ctx.Done():
}
}
}
......@@ -66,9 +75,10 @@ func discoverUPNP_IG1() <-chan NAT {
return res
}
func discoverUPNP_IG2() <-chan NAT {
res := make(chan NAT, 1)
func discoverUPNP_IG2(ctx context.Context) <-chan NAT {
res := make(chan NAT)
go func() {
defer close(res)
// find devices
devs, err := goupnp.DiscoverDevices(internetgateway2.URN_WANConnectionDevice_2)
......@@ -82,6 +92,9 @@ func discoverUPNP_IG2() <-chan NAT {
}
dev.Root.Device.VisitServices(func(srv *goupnp.Service) {
if ctx.Err() != nil {
return
}
switch srv.ServiceType {
case internetgateway2.URN_WANIPConnection_1:
client := &internetgateway2.WANIPConnection1{ServiceClient: goupnp.ServiceClient{
......@@ -91,8 +104,10 @@ func discoverUPNP_IG2() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-IP1)", dev.Root}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-IP1)", dev.Root}:
case <-ctx.Done():
}
}
case internetgateway2.URN_WANIPConnection_2:
......@@ -103,8 +118,10 @@ func discoverUPNP_IG2() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-IP2)", dev.Root}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-IP2)", dev.Root}:
case <-ctx.Done():
}
}
case internetgateway2.URN_WANPPPConnection_1:
......@@ -115,8 +132,10 @@ func discoverUPNP_IG2() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-PPP1)", dev.Root}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-PPP1)", dev.Root}:
case <-ctx.Done():
}
}
}
......@@ -127,9 +146,11 @@ func discoverUPNP_IG2() <-chan NAT {
return res
}
func discoverUPNP_GenIGDev() <-chan NAT {
func discoverUPNP_GenIGDev(ctx context.Context) <-chan NAT {
res := make(chan NAT, 1)
go func() {
defer close(res)
DeviceList, err := ssdp.Search(ssdp.All, 5, "")
if err != nil {
return
......@@ -152,6 +173,9 @@ func discoverUPNP_GenIGDev() <-chan NAT {
}
RootDevice.Device.VisitServices(func(srv *goupnp.Service) {
if ctx.Err() != nil {
return
}
switch srv.ServiceType {
case internetgateway1.URN_WANIPConnection_1:
client := &internetgateway1.WANIPConnection1{ServiceClient: goupnp.ServiceClient{
......@@ -161,8 +185,10 @@ func discoverUPNP_GenIGDev() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-IP1)", RootDevice}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-IP1)", RootDevice}:
case <-ctx.Done():
}
}
case internetgateway1.URN_WANPPPConnection_1:
......@@ -173,8 +199,10 @@ func discoverUPNP_GenIGDev() <-chan NAT {
}}
_, isNat, err := client.GetNATRSIPStatus()
if err == nil && isNat {
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-PPP1)", RootDevice}
return
select {
case res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-PPP1)", RootDevice}:
case <-ctx.Done():
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment