github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/nat/natupnp.go (about) 1 package nat 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "strings" 8 "time" 9 10 "github.com/fjl/goupnp" 11 "github.com/fjl/goupnp/dcps/internetgateway1" 12 "github.com/fjl/goupnp/dcps/internetgateway2" 13 ) 14 15 type upnp struct { 16 dev *goupnp.RootDevice 17 service string 18 client upnpClient 19 } 20 21 type upnpClient interface { 22 GetExternalIPAddress() (string, error) 23 AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error 24 DeletePortMapping(string, uint16, string) error 25 GetNATRSIPStatus() (sip bool, nat bool, err error) 26 } 27 28 func (n *upnp) ExternalIP() (addr net.IP, err error) { 29 ipString, err := n.client.GetExternalIPAddress() 30 if err != nil { 31 return nil, err 32 } 33 ip := net.ParseIP(ipString) 34 if ip == nil { 35 return nil, errors.New("bad IP in response") 36 } 37 return ip, nil 38 } 39 40 func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error { 41 ip, err := n.internalAddress() 42 if err != nil { 43 return nil 44 } 45 protocol = strings.ToUpper(protocol) 46 lifetimeS := uint32(lifetime / time.Second) 47 return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) 48 } 49 50 func (n *upnp) internalAddress() (net.IP, error) { 51 devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host) 52 if err != nil { 53 return nil, err 54 } 55 ifaces, err := net.Interfaces() 56 if err != nil { 57 return nil, err 58 } 59 for _, iface := range ifaces { 60 addrs, err := iface.Addrs() 61 if err != nil { 62 return nil, err 63 } 64 for _, addr := range addrs { 65 switch x := addr.(type) { 66 case *net.IPNet: 67 if x.Contains(devaddr.IP) { 68 return x.IP, nil 69 } 70 } 71 } 72 } 73 return nil, fmt.Errorf("could not find local address in same net as %v", devaddr) 74 } 75 76 func (n *upnp) DeleteMapping(protocol string, extport, intport int) error { 77 return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol)) 78 } 79 80 func (n *upnp) String() string { 81 return "UPNP " + n.service 82 } 83 84 // discoverUPnP searches for Internet Gateway Devices 85 // and returns the first one it can find on the local network. 86 func discoverUPnP() Interface { 87 found := make(chan *upnp, 2) 88 // IGDv1 89 go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { 90 switch sc.Service.ServiceType { 91 case internetgateway1.URN_WANIPConnection_1: 92 return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{sc}} 93 case internetgateway1.URN_WANPPPConnection_1: 94 return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{sc}} 95 } 96 return nil 97 }) 98 // IGDv2 99 go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { 100 switch sc.Service.ServiceType { 101 case internetgateway2.URN_WANIPConnection_1: 102 return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{sc}} 103 case internetgateway2.URN_WANIPConnection_2: 104 return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{sc}} 105 case internetgateway2.URN_WANPPPConnection_1: 106 return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{sc}} 107 } 108 return nil 109 }) 110 for i := 0; i < cap(found); i++ { 111 if c := <-found; c != nil { 112 return c 113 } 114 } 115 return nil 116 } 117 118 func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) { 119 devs, err := goupnp.DiscoverDevices(target) 120 if err != nil { 121 return 122 } 123 found := false 124 for i := 0; i < len(devs) && !found; i++ { 125 if devs[i].Root == nil { 126 continue 127 } 128 devs[i].Root.Device.VisitServices(func(service *goupnp.Service) { 129 if found { 130 return 131 } 132 // check for a matching IGD service 133 sc := goupnp.ServiceClient{service.NewSOAPClient(), devs[i].Root, service} 134 upnp := matcher(devs[i].Root, sc) 135 if upnp == nil { 136 return 137 } 138 // check whether port mapping is enabled 139 if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat { 140 return 141 } 142 out <- upnp 143 found = true 144 }) 145 } 146 if !found { 147 out <- nil 148 } 149 }