@@ -21,19 +21,25 @@ import (
21
21
"fmt"
22
22
"net"
23
23
"strings"
24
+ "sync"
24
25
"time"
25
26
26
27
"github.com/huin/goupnp"
27
28
"github.com/huin/goupnp/dcps/internetgateway1"
28
29
"github.com/huin/goupnp/dcps/internetgateway2"
29
30
)
30
31
31
- const soapRequestTimeout = 3 * time .Second
32
+ const (
33
+ soapRequestTimeout = 3 * time .Second
34
+ rateLimit = 200 * time .Millisecond
35
+ )
32
36
33
37
type 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
37
43
}
38
44
39
45
type upnpClient interface {
@@ -43,8 +49,23 @@ type upnpClient interface {
43
49
GetNATRSIPStatus () (sip bool , nat bool , err error )
44
50
}
45
51
52
+ func (n * upnp ) natEnabled () bool {
53
+ var ok bool
54
+ var err error
55
+ n .withRateLimit (func () error {
56
+ _ , ok , err = n .client .GetNATRSIPStatus ()
57
+ return err
58
+ })
59
+ return err == nil && ok
60
+ }
61
+
46
62
func (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
+
48
69
if err != nil {
49
70
return nil , err
50
71
}
@@ -63,7 +84,10 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
63
84
protocol = strings .ToUpper (protocol )
64
85
lifetimeS := uint32 (lifetime / time .Second )
65
86
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
+ })
67
91
}
68
92
69
93
func (n * upnp ) internalAddress () (net.IP , error ) {
@@ -90,36 +114,51 @@ func (n *upnp) internalAddress() (net.IP, error) {
90
114
}
91
115
92
116
func (n * upnp ) DeleteMapping (protocol string , extport , intport int ) error {
93
- return n .client .DeletePortMapping ("" , uint16 (extport ), strings .ToUpper (protocol ))
117
+ return n .withRateLimit (func () error {
118
+ return n .client .DeletePortMapping ("" , uint16 (extport ), strings .ToUpper (protocol ))
119
+ })
94
120
}
95
121
96
122
func (n * upnp ) String () string {
97
123
return "UPNP " + n .service
98
124
}
99
125
126
+ func (n * upnp ) withRateLimit (fn func () error ) error {
127
+ n .mu .Lock ()
128
+ defer n .mu .Unlock ()
129
+
130
+ lastreq := time .Since (n .lastReqTime )
131
+ if lastreq < rateLimit {
132
+ time .Sleep (rateLimit - lastreq )
133
+ }
134
+ err := fn ()
135
+ n .lastReqTime = time .Now ()
136
+ return err
137
+ }
138
+
100
139
// discoverUPnP searches for Internet Gateway Devices
101
140
// and returns the first one it can find on the local network.
102
141
func discoverUPnP () Interface {
103
142
found := make (chan * upnp , 2 )
104
143
// IGDv1
105
- go discover (found , internetgateway1 .URN_WANConnectionDevice_1 , func (dev * goupnp. RootDevice , sc goupnp.ServiceClient ) * upnp {
144
+ go discover (found , internetgateway1 .URN_WANConnectionDevice_1 , func (sc goupnp.ServiceClient ) * upnp {
106
145
switch sc .Service .ServiceType {
107
146
case internetgateway1 .URN_WANIPConnection_1 :
108
- return & upnp {dev , "IGDv1-IP1" , & internetgateway1.WANIPConnection1 {ServiceClient : sc }}
147
+ return & upnp {service : "IGDv1-IP1" , client : & internetgateway1.WANIPConnection1 {ServiceClient : sc }}
109
148
case internetgateway1 .URN_WANPPPConnection_1 :
110
- return & upnp {dev , "IGDv1-PPP1" , & internetgateway1.WANPPPConnection1 {ServiceClient : sc }}
149
+ return & upnp {service : "IGDv1-PPP1" , client : & internetgateway1.WANPPPConnection1 {ServiceClient : sc }}
111
150
}
112
151
return nil
113
152
})
114
153
// IGDv2
115
- go discover (found , internetgateway2 .URN_WANConnectionDevice_2 , func (dev * goupnp. RootDevice , sc goupnp.ServiceClient ) * upnp {
154
+ go discover (found , internetgateway2 .URN_WANConnectionDevice_2 , func (sc goupnp.ServiceClient ) * upnp {
116
155
switch sc .Service .ServiceType {
117
156
case internetgateway2 .URN_WANIPConnection_1 :
118
- return & upnp {dev , "IGDv2-IP1" , & internetgateway2.WANIPConnection1 {ServiceClient : sc }}
157
+ return & upnp {service : "IGDv2-IP1" , client : & internetgateway2.WANIPConnection1 {ServiceClient : sc }}
119
158
case internetgateway2 .URN_WANIPConnection_2 :
120
- return & upnp {dev , "IGDv2-IP2" , & internetgateway2.WANIPConnection2 {ServiceClient : sc }}
159
+ return & upnp {service : "IGDv2-IP2" , client : & internetgateway2.WANIPConnection2 {ServiceClient : sc }}
121
160
case internetgateway2 .URN_WANPPPConnection_1 :
122
- return & upnp {dev , "IGDv2-PPP1" , & internetgateway2.WANPPPConnection1 {ServiceClient : sc }}
161
+ return & upnp {service : "IGDv2-PPP1" , client : & internetgateway2.WANPPPConnection1 {ServiceClient : sc }}
123
162
}
124
163
return nil
125
164
})
@@ -134,7 +173,7 @@ func discoverUPnP() Interface {
134
173
// finds devices matching the given target and calls matcher for all
135
174
// advertised services of each device. The first non-nil service found
136
175
// is sent into out. If no service matched, nil is sent.
137
- func discover (out chan <- * upnp , target string , matcher func (* goupnp. RootDevice , goupnp.ServiceClient ) * upnp ) {
176
+ func discover (out chan <- * upnp , target string , matcher func (goupnp.ServiceClient ) * upnp ) {
138
177
devs , err := goupnp .DiscoverDevices (target )
139
178
if err != nil {
140
179
out <- nil
@@ -157,16 +196,17 @@ func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice,
157
196
Service : service ,
158
197
}
159
198
sc .SOAPClient .HTTPClient .Timeout = soapRequestTimeout
160
- upnp := matcher (devs [ i ]. Root , sc )
199
+ upnp := matcher (sc )
161
200
if upnp == nil {
162
201
return
163
202
}
203
+ upnp .dev = devs [i ].Root
204
+
164
205
// check whether port mapping is enabled
165
- if _ , nat , err := upnp .client .GetNATRSIPStatus (); err != nil || ! nat {
166
- return
206
+ if upnp .natEnabled () {
207
+ out <- upnp
208
+ found = true
167
209
}
168
- out <- upnp
169
- found = true
170
210
})
171
211
}
172
212
if ! found {
0 commit comments