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