116 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			116 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | package nat | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"fmt" | ||
|  | 	"net" | ||
|  | 	"strings" | ||
|  | 	"time" | ||
|  | 
 | ||
|  | 	"github.com/jackpal/go-nat-pmp" | ||
|  | ) | ||
|  | 
 | ||
|  | // natPMPClient adapts the NAT-PMP protocol implementation so it conforms to | ||
|  | // the common interface. | ||
|  | type pmp struct { | ||
|  | 	gw net.IP | ||
|  | 	c  *natpmp.Client | ||
|  | } | ||
|  | 
 | ||
|  | func (n *pmp) String() string { | ||
|  | 	return fmt.Sprintf("NAT-PMP(%v)", n.gw) | ||
|  | } | ||
|  | 
 | ||
|  | func (n *pmp) ExternalIP() (net.IP, error) { | ||
|  | 	response, err := n.c.GetExternalAddress() | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	return response.ExternalIPAddress[:], nil | ||
|  | } | ||
|  | 
 | ||
|  | func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { | ||
|  | 	if lifetime <= 0 { | ||
|  | 		return fmt.Errorf("lifetime must not be <= 0") | ||
|  | 	} | ||
|  | 	// Note order of port arguments is switched between our | ||
|  | 	// AddMapping and the client's AddPortMapping. | ||
|  | 	_, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second)) | ||
|  | 	return err | ||
|  | } | ||
|  | 
 | ||
|  | func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) { | ||
|  | 	// To destroy a mapping, send an add-port with an internalPort of | ||
|  | 	// the internal port to destroy, an external port of zero and a | ||
|  | 	// time of zero. | ||
|  | 	_, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0) | ||
|  | 	return err | ||
|  | } | ||
|  | 
 | ||
|  | func discoverPMP() Interface { | ||
|  | 	// run external address lookups on all potential gateways | ||
|  | 	gws := potentialGateways() | ||
|  | 	found := make(chan *pmp, len(gws)) | ||
|  | 	for i := range gws { | ||
|  | 		gw := gws[i] | ||
|  | 		go func() { | ||
|  | 			c := natpmp.NewClient(gw) | ||
|  | 			if _, err := c.GetExternalAddress(); err != nil { | ||
|  | 				found <- nil | ||
|  | 			} else { | ||
|  | 				found <- &pmp{gw, c} | ||
|  | 			} | ||
|  | 		}() | ||
|  | 	} | ||
|  | 	// return the one that responds first. | ||
|  | 	// discovery needs to be quick, so we stop caring about | ||
|  | 	// any responses after a very short timeout. | ||
|  | 	timeout := time.NewTimer(1 * time.Second) | ||
|  | 	defer timeout.Stop() | ||
|  | 	for _ = range gws { | ||
|  | 		select { | ||
|  | 		case c := <-found: | ||
|  | 			if c != nil { | ||
|  | 				return c | ||
|  | 			} | ||
|  | 		case <-timeout.C: | ||
|  | 			return nil | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | var ( | ||
|  | 	// LAN IP ranges | ||
|  | 	_, lan10, _  = net.ParseCIDR("10.0.0.0/8") | ||
|  | 	_, lan176, _ = net.ParseCIDR("172.16.0.0/12") | ||
|  | 	_, lan192, _ = net.ParseCIDR("192.168.0.0/16") | ||
|  | ) | ||
|  | 
 | ||
|  | // TODO: improve this. We currently assume that (on most networks) | ||
|  | // the router is X.X.X.1 in a local LAN range. | ||
|  | func potentialGateways() (gws []net.IP) { | ||
|  | 	ifaces, err := net.Interfaces() | ||
|  | 	if err != nil { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	for _, iface := range ifaces { | ||
|  | 		ifaddrs, err := iface.Addrs() | ||
|  | 		if err != nil { | ||
|  | 			return gws | ||
|  | 		} | ||
|  | 		for _, addr := range ifaddrs { | ||
|  | 			switch x := addr.(type) { | ||
|  | 			case *net.IPNet: | ||
|  | 				if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) { | ||
|  | 					ip := x.IP.Mask(x.Mask).To4() | ||
|  | 					if ip != nil { | ||
|  | 						ip[3] = ip[3] | 0x01 | ||
|  | 						gws = append(gws, ip) | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return gws | ||
|  | } |