@@ -21,19 +21,25 @@ import (
2121 "fmt"
2222 "net"
2323 "strings"
24+ "sync"
2425 "time"
2526
2627 "github.com/huin/goupnp"
2728 "github.com/huin/goupnp/dcps/internetgateway1"
2829 "github.com/huin/goupnp/dcps/internetgateway2"
2930)
3031
31- const soapRequestTimeout = 3 * time .Second
32+ const (
33+ soapRequestTimeout = 3 * time .Second
34+ rateLimit = 200 * time .Millisecond
35+ )
3236
3337type upnp struct {
34- dev * goupnp.RootDevice
35- service string
36- client upnpClient
38+ dev * goupnp.RootDevice
39+ service string
40+ client upnpClient
41+ mu sync.Mutex
42+ lastReqTime time.Time
3743}
3844
3945type upnpClient interface {
@@ -43,8 +49,23 @@ type upnpClient interface {
4349 GetNATRSIPStatus () (sip bool , nat bool , err error )
4450}
4551
52+ func (n * upnp ) natEnabled () bool {
53+ var nat bool
54+ err := n .withRateLimit (func () error {
55+ var innerErr error
56+ _ , nat , innerErr = n .client .GetNATRSIPStatus ()
57+ return innerErr
58+ })
59+ return err == nil && nat
60+ }
61+
4662func (n * upnp ) ExternalIP () (addr net.IP , err error ) {
47- ipString , err := n .client .GetExternalIPAddress ()
63+ var ipString string
64+ n .withRateLimit (func () error {
65+ ipString , err = n .client .GetExternalIPAddress ()
66+ return err
67+ })
68+
4869 if err != nil {
4970 return nil , err
5071 }
@@ -63,7 +84,10 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
6384 protocol = strings .ToUpper (protocol )
6485 lifetimeS := uint32 (lifetime / time .Second )
6586 n .DeleteMapping (protocol , extport , intport )
66- return n .client .AddPortMapping ("" , uint16 (extport ), protocol , uint16 (intport ), ip .String (), true , desc , lifetimeS )
87+
88+ return n .withRateLimit (func () error {
89+ return n .client .AddPortMapping ("" , uint16 (extport ), protocol , uint16 (intport ), ip .String (), true , desc , lifetimeS )
90+ })
6791}
6892
6993func (n * upnp ) internalAddress () (net.IP , error ) {
@@ -93,36 +117,51 @@ func (n *upnp) internalAddress() (net.IP, error) {
93117}
94118
95119func (n * upnp ) DeleteMapping (protocol string , extport , intport int ) error {
96- return n .client .DeletePortMapping ("" , uint16 (extport ), strings .ToUpper (protocol ))
120+ return n .withRateLimit (func () error {
121+ return n .client .DeletePortMapping ("" , uint16 (extport ), strings .ToUpper (protocol ))
122+ })
97123}
98124
99125func (n * upnp ) String () string {
100126 return "UPNP " + n .service
101127}
102128
129+ func (n * upnp ) withRateLimit (fn func () error ) error {
130+ n .mu .Lock ()
131+ defer n .mu .Unlock ()
132+
133+ lastreq := time .Since (n .lastReqTime )
134+ if lastreq < rateLimit {
135+ time .Sleep (rateLimit - lastreq )
136+ }
137+ err := fn ()
138+ n .lastReqTime = time .Now ()
139+ return err
140+ }
141+
103142// discoverUPnP searches for Internet Gateway Devices
104143// and returns the first one it can find on the local network.
105144func discoverUPnP () Interface {
106145 found := make (chan * upnp , 2 )
107146 // IGDv1
108- go discover (found , internetgateway1 .URN_WANConnectionDevice_1 , func (dev * goupnp. RootDevice , sc goupnp.ServiceClient ) * upnp {
147+ go discover (found , internetgateway1 .URN_WANConnectionDevice_1 , func (sc goupnp.ServiceClient ) * upnp {
109148 switch sc .Service .ServiceType {
110149 case internetgateway1 .URN_WANIPConnection_1 :
111- return & upnp {dev , "IGDv1-IP1" , & internetgateway1.WANIPConnection1 {ServiceClient : sc }}
150+ return & upnp {service : "IGDv1-IP1" , client : & internetgateway1.WANIPConnection1 {ServiceClient : sc }}
112151 case internetgateway1 .URN_WANPPPConnection_1 :
113- return & upnp {dev , "IGDv1-PPP1" , & internetgateway1.WANPPPConnection1 {ServiceClient : sc }}
152+ return & upnp {service : "IGDv1-PPP1" , client : & internetgateway1.WANPPPConnection1 {ServiceClient : sc }}
114153 }
115154 return nil
116155 })
117156 // IGDv2
118- go discover (found , internetgateway2 .URN_WANConnectionDevice_2 , func (dev * goupnp. RootDevice , sc goupnp.ServiceClient ) * upnp {
157+ go discover (found , internetgateway2 .URN_WANConnectionDevice_2 , func (sc goupnp.ServiceClient ) * upnp {
119158 switch sc .Service .ServiceType {
120159 case internetgateway2 .URN_WANIPConnection_1 :
121- return & upnp {dev , "IGDv2-IP1" , & internetgateway2.WANIPConnection1 {ServiceClient : sc }}
160+ return & upnp {service : "IGDv2-IP1" , client : & internetgateway2.WANIPConnection1 {ServiceClient : sc }}
122161 case internetgateway2 .URN_WANIPConnection_2 :
123- return & upnp {dev , "IGDv2-IP2" , & internetgateway2.WANIPConnection2 {ServiceClient : sc }}
162+ return & upnp {service : "IGDv2-IP2" , client : & internetgateway2.WANIPConnection2 {ServiceClient : sc }}
124163 case internetgateway2 .URN_WANPPPConnection_1 :
125- return & upnp {dev , "IGDv2-PPP1" , & internetgateway2.WANPPPConnection1 {ServiceClient : sc }}
164+ return & upnp {service : "IGDv2-PPP1" , client : & internetgateway2.WANPPPConnection1 {ServiceClient : sc }}
126165 }
127166 return nil
128167 })
@@ -137,7 +176,7 @@ func discoverUPnP() Interface {
137176// finds devices matching the given target and calls matcher for all
138177// advertised services of each device. The first non-nil service found
139178// is sent into out. If no service matched, nil is sent.
140- func discover (out chan <- * upnp , target string , matcher func (* goupnp. RootDevice , goupnp.ServiceClient ) * upnp ) {
179+ func discover (out chan <- * upnp , target string , matcher func (goupnp.ServiceClient ) * upnp ) {
141180 devs , err := goupnp .DiscoverDevices (target )
142181 if err != nil {
143182 out <- nil
@@ -160,16 +199,17 @@ func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice,
160199 Service : service ,
161200 }
162201 sc .SOAPClient .HTTPClient .Timeout = soapRequestTimeout
163- upnp := matcher (devs [ i ]. Root , sc )
202+ upnp := matcher (sc )
164203 if upnp == nil {
165204 return
166205 }
206+ upnp .dev = devs [i ].Root
207+
167208 // check whether port mapping is enabled
168- if _ , nat , err := upnp .client .GetNATRSIPStatus (); err != nil || ! nat {
169- return
209+ if upnp .natEnabled () {
210+ out <- upnp
211+ found = true
170212 }
171- out <- upnp
172- found = true
173213 })
174214 }
175215 if ! found {
0 commit comments