@@ -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,24 @@ 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+ err = n .withRateLimit (func () error {
65+ var innerErr error
66+ ipString , innerErr = n .client .GetExternalIPAddress ()
67+ return innerErr
68+ })
69+
4870 if err != nil {
4971 return nil , err
5072 }
@@ -63,7 +85,10 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
6385 protocol = strings .ToUpper (protocol )
6486 lifetimeS := uint32 (lifetime / time .Second )
6587 n .DeleteMapping (protocol , extport , intport )
66- return n .client .AddPortMapping ("" , uint16 (extport ), protocol , uint16 (intport ), ip .String (), true , desc , lifetimeS )
88+
89+ return n .withRateLimit (func () error {
90+ return n .client .AddPortMapping ("" , uint16 (extport ), protocol , uint16 (intport ), ip .String (), true , desc , lifetimeS )
91+ })
6792}
6893
6994func (n * upnp ) internalAddress () (net.IP , error ) {
@@ -93,36 +118,51 @@ func (n *upnp) internalAddress() (net.IP, error) {
93118}
94119
95120func (n * upnp ) DeleteMapping (protocol string , extport , intport int ) error {
96- return n .client .DeletePortMapping ("" , uint16 (extport ), strings .ToUpper (protocol ))
121+ return n .withRateLimit (func () error {
122+ return n .client .DeletePortMapping ("" , uint16 (extport ), strings .ToUpper (protocol ))
123+ })
97124}
98125
99126func (n * upnp ) String () string {
100127 return "UPNP " + n .service
101128}
102129
130+ func (n * upnp ) withRateLimit (fn func () error ) error {
131+ n .mu .Lock ()
132+ defer n .mu .Unlock ()
133+
134+ lastreq := time .Since (n .lastReqTime )
135+ if lastreq < rateLimit {
136+ time .Sleep (rateLimit - lastreq )
137+ }
138+ err := fn ()
139+ n .lastReqTime = time .Now ()
140+ return err
141+ }
142+
103143// discoverUPnP searches for Internet Gateway Devices
104144// and returns the first one it can find on the local network.
105145func discoverUPnP () Interface {
106146 found := make (chan * upnp , 2 )
107147 // IGDv1
108- go discover (found , internetgateway1 .URN_WANConnectionDevice_1 , func (dev * goupnp. RootDevice , sc goupnp.ServiceClient ) * upnp {
148+ go discover (found , internetgateway1 .URN_WANConnectionDevice_1 , func (sc goupnp.ServiceClient ) * upnp {
109149 switch sc .Service .ServiceType {
110150 case internetgateway1 .URN_WANIPConnection_1 :
111- return & upnp {dev , "IGDv1-IP1" , & internetgateway1.WANIPConnection1 {ServiceClient : sc }}
151+ return & upnp {service : "IGDv1-IP1" , client : & internetgateway1.WANIPConnection1 {ServiceClient : sc }}
112152 case internetgateway1 .URN_WANPPPConnection_1 :
113- return & upnp {dev , "IGDv1-PPP1" , & internetgateway1.WANPPPConnection1 {ServiceClient : sc }}
153+ return & upnp {service : "IGDv1-PPP1" , client : & internetgateway1.WANPPPConnection1 {ServiceClient : sc }}
114154 }
115155 return nil
116156 })
117157 // IGDv2
118- go discover (found , internetgateway2 .URN_WANConnectionDevice_2 , func (dev * goupnp. RootDevice , sc goupnp.ServiceClient ) * upnp {
158+ go discover (found , internetgateway2 .URN_WANConnectionDevice_2 , func (sc goupnp.ServiceClient ) * upnp {
119159 switch sc .Service .ServiceType {
120160 case internetgateway2 .URN_WANIPConnection_1 :
121- return & upnp {dev , "IGDv2-IP1" , & internetgateway2.WANIPConnection1 {ServiceClient : sc }}
161+ return & upnp {service : "IGDv2-IP1" , client : & internetgateway2.WANIPConnection1 {ServiceClient : sc }}
122162 case internetgateway2 .URN_WANIPConnection_2 :
123- return & upnp {dev , "IGDv2-IP2" , & internetgateway2.WANIPConnection2 {ServiceClient : sc }}
163+ return & upnp {service : "IGDv2-IP2" , client : & internetgateway2.WANIPConnection2 {ServiceClient : sc }}
124164 case internetgateway2 .URN_WANPPPConnection_1 :
125- return & upnp {dev , "IGDv2-PPP1" , & internetgateway2.WANPPPConnection1 {ServiceClient : sc }}
165+ return & upnp {service : "IGDv2-PPP1" , client : & internetgateway2.WANPPPConnection1 {ServiceClient : sc }}
126166 }
127167 return nil
128168 })
@@ -137,7 +177,7 @@ func discoverUPnP() Interface {
137177// finds devices matching the given target and calls matcher for all
138178// advertised services of each device. The first non-nil service found
139179// 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 ) {
180+ func discover (out chan <- * upnp , target string , matcher func (goupnp.ServiceClient ) * upnp ) {
141181 devs , err := goupnp .DiscoverDevices (target )
142182 if err != nil {
143183 out <- nil
@@ -160,16 +200,17 @@ func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice,
160200 Service : service ,
161201 }
162202 sc .SOAPClient .HTTPClient .Timeout = soapRequestTimeout
163- upnp := matcher (devs [ i ]. Root , sc )
203+ upnp := matcher (sc )
164204 if upnp == nil {
165205 return
166206 }
207+ upnp .dev = devs [i ].Root
208+
167209 // check whether port mapping is enabled
168- if _ , nat , err := upnp .client .GetNATRSIPStatus (); err != nil || ! nat {
169- return
210+ if upnp .natEnabled () {
211+ out <- upnp
212+ found = true
170213 }
171- out <- upnp
172- found = true
173214 })
174215 }
175216 if ! found {
0 commit comments