diff options
Diffstat (limited to 'src/upnp')
-rw-r--r-- | src/upnp/Makefile.am | 59 | ||||
-rw-r--r-- | src/upnp/draft-cheshire-nat-pmp.txt | 1160 | ||||
-rw-r--r-- | src/upnp/test_upnp.c | 110 | ||||
-rw-r--r-- | src/upnp/upnp.c | 721 | ||||
-rw-r--r-- | src/upnp/upnp.h | 82 | ||||
-rw-r--r-- | src/upnp/upnp_init.c | 208 | ||||
-rw-r--r-- | src/upnp/upnp_ip.c | 47 | ||||
-rw-r--r-- | src/upnp/upnp_ip.h | 40 | ||||
-rw-r--r-- | src/upnp/upnp_util.c | 152 | ||||
-rw-r--r-- | src/upnp/upnp_util.h | 68 | ||||
-rw-r--r-- | src/upnp/upnp_xmlnode.c | 487 | ||||
-rw-r--r-- | src/upnp/upnp_xmlnode.h | 92 |
12 files changed, 3226 insertions, 0 deletions
diff --git a/src/upnp/Makefile.am b/src/upnp/Makefile.am new file mode 100644 index 000000000..02a2b139d --- /dev/null +++ b/src/upnp/Makefile.am | |||
@@ -0,0 +1,59 @@ | |||
1 | INCLUDES = -I$(top_srcdir)/src/include | ||
2 | |||
3 | if MINGW | ||
4 | WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols | ||
5 | endif | ||
6 | |||
7 | if USE_COVERAGE | ||
8 | AM_CFLAGS = -fprofile-arcs -ftest-coverage | ||
9 | endif | ||
10 | |||
11 | lib_LTLIBRARIES = libgnunetupnp.la | ||
12 | |||
13 | libgnunetupnp_la_SOURCES = \ | ||
14 | upnp.c upnp.h \ | ||
15 | upnp_ip.c upnp_ip.h \ | ||
16 | upnp_util.c upnp_util.h \ | ||
17 | upnp_xmlnode.c upnp_xmlnode.h | ||
18 | libgnunetupnp_la_CFLAGS = \ | ||
19 | -I$(top_scrdir)/include \ | ||
20 | @LIBCURL_CPPFLAGS@ @XML_CPPFLAGS@ | ||
21 | libgnunetupnp_la_LIBADD = \ | ||
22 | $(top_builddir)/src/util/libgnunetutil.la \ | ||
23 | $(GN_LIBINTL) @EXT_LIBS@ @XML_LIBS@ @LIBCURL@ | ||
24 | libgnunetupnp_la_LDFLAGS = \ | ||
25 | $(GN_LIB_LDFLAGS) $(WINFLAGS) \ | ||
26 | -version-info 0:0:0 | ||
27 | |||
28 | |||
29 | bin_PROGRAMS = \ | ||
30 | gnunet-upnp \ | ||
31 | gnunet-service-upnp | ||
32 | |||
33 | gnunet_upnp_SOURCES = \ | ||
34 | gnunet-upnp.c | ||
35 | gnunet_upnp_LDADD = \ | ||
36 | $(top_builddir)/src/upnp/libgnunetupnp.la \ | ||
37 | $(top_builddir)/src/util/libgnunetutil.la \ | ||
38 | $(GN_LIBINTL) | ||
39 | |||
40 | gnunet_service_upnp_SOURCES = \ | ||
41 | gnunet-service-upnp.c | ||
42 | gnunet_service_upnp_LDADD = \ | ||
43 | $(top_builddir)/src/util/libgnunetutil.la \ | ||
44 | $(GN_LIBINTL) | ||
45 | |||
46 | |||
47 | check_PROGRAMS = \ | ||
48 | test_upnp | ||
49 | |||
50 | TESTS = $(check_PROGRAMS) | ||
51 | |||
52 | test_upnp_SOURCES = \ | ||
53 | test_upnp.c | ||
54 | test_upnp_LDADD = \ | ||
55 | $(top_builddir)/src/transport/libgnunetupnp.la \ | ||
56 | $(top_builddir)/src/util/libgnunetutil.la | ||
57 | |||
58 | |||
59 | # EXTRA_DIST = test_upnp_data.conf | ||
diff --git a/src/upnp/draft-cheshire-nat-pmp.txt b/src/upnp/draft-cheshire-nat-pmp.txt new file mode 100644 index 000000000..727b5fad7 --- /dev/null +++ b/src/upnp/draft-cheshire-nat-pmp.txt | |||
@@ -0,0 +1,1160 @@ | |||
1 | Document: draft-cheshire-nat-pmp-02.txt Stuart Cheshire | ||
2 | Internet-Draft Marc Krochmal | ||
3 | Category: Standards Track Apple Computer, Inc. | ||
4 | Expires 14th March 2007 Kiren Sekar | ||
5 | Sharpcast, Inc. | ||
6 | 14th September 2006 | ||
7 | |||
8 | NAT Port Mapping Protocol (NAT-PMP) | ||
9 | |||
10 | <draft-cheshire-nat-pmp-02.txt> | ||
11 | |||
12 | Status of this Memo | ||
13 | |||
14 | By submitting this Internet-Draft, each author represents that any | ||
15 | applicable patent or other IPR claims of which he or she is aware | ||
16 | have been or will be disclosed, and any of which he or she becomes | ||
17 | aware will be disclosed, in accordance with Section 6 of BCP 79. | ||
18 | For the purposes of this document, the term "BCP 79" refers | ||
19 | exclusively to RFC 3979, "Intellectual Property Rights in IETF | ||
20 | Technology", published March 2005. | ||
21 | |||
22 | Internet-Drafts are working documents of the Internet Engineering | ||
23 | Task Force (IETF), its areas, and its working groups. Note that | ||
24 | other groups may also distribute working documents as Internet- | ||
25 | Drafts. | ||
26 | |||
27 | Internet-Drafts are draft documents valid for a maximum of six months | ||
28 | and may be updated, replaced, or obsoleted by other documents at any | ||
29 | time. It is inappropriate to use Internet-Drafts as reference | ||
30 | material or to cite them other than as "work in progress." | ||
31 | |||
32 | The list of current Internet-Drafts can be accessed at | ||
33 | http://www.ietf.org/1id-abstracts.html | ||
34 | |||
35 | The list of Internet-Draft Shadow Directories can be accessed at | ||
36 | http://www.ietf.org/shadow.html | ||
37 | |||
38 | |||
39 | Abstract | ||
40 | |||
41 | This document describes a protocol for automating the process of | ||
42 | creating Network Address Translation (NAT) port mappings. Included | ||
43 | in the protocol is a method for retrieving the public IP address of | ||
44 | a NAT gateway, thus allowing a client to make this public IP address | ||
45 | and port number known to peers that may wish to communicate with it. | ||
46 | This protocol is implemented in current Apple products including | ||
47 | Mac OS X, Bonjour for Windows, and AirPort wireless base stations. | ||
48 | |||
49 | |||
50 | |||
51 | |||
52 | |||
53 | |||
54 | |||
55 | |||
56 | |||
57 | |||
58 | Expires 14th March 2007 Cheshire, et al. [Page 1] | ||
59 | |||
60 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
61 | |||
62 | |||
63 | 1. Introduction | ||
64 | |||
65 | Network Address Translation (NAT) is a method of sharing one public | ||
66 | internet address with a number of devices. This document is focused | ||
67 | on what "IP Network Address Translator (NAT) Terminology and | ||
68 | Considerations" [RFC 2663] calls "NAPTs" (Network Address/Port | ||
69 | Translators). A full description of NAT is beyond the scope of this | ||
70 | document. The following brief overview will cover the aspects | ||
71 | relevant to this port mapping protocol. For more information on | ||
72 | NAT, see "Traditional IP Network Address Translator" [RFC 3022]. | ||
73 | |||
74 | NATs have one or more public IP addresses. A private network is set | ||
75 | up behind the NAT. Devices behind the NAT are assigned private | ||
76 | addresses and the private address of the NAT device is used as the | ||
77 | gateway. | ||
78 | |||
79 | When a packet from any device behind the NAT is sent to an address on | ||
80 | the public internet, the packet first passes through the NAT box. The | ||
81 | NAT box looks at the source port and address. In some cases, a NAT | ||
82 | will also keep track of the destination port and address. The NAT | ||
83 | then creates a mapping from the private address and private port to a | ||
84 | public address and public port if a mapping does not already exist. | ||
85 | The NAT box replaces the private address and port number in the | ||
86 | packet with the public entries from the mapping and sends the packet | ||
87 | on to the next gateway. | ||
88 | |||
89 | When a packet from any address on the internet is received on the | ||
90 | NAT's public side, the NAT will look up the destination port (public | ||
91 | port) in the list of mappings. If an entry is found, it will contain | ||
92 | the private address and port that the packet should be sent to. The | ||
93 | NAT gateway will then rewrite the destination address and port with | ||
94 | those from the mapping. The packet will then be forwarded to the new | ||
95 | destination addresses. If the packet did not match any mapping, the | ||
96 | packet will most likely be dropped. Various NATs implement different | ||
97 | strategies to handle this. The important thing to note is that if | ||
98 | there is no mapping, the NAT does not know which private address the | ||
99 | packet should be sent to. | ||
100 | |||
101 | Mappings are usually created automatically as a result of observing | ||
102 | outbound traffic. There are a few exceptions. Some NATs may allow | ||
103 | manually-created permanent mappings that map a public port to a | ||
104 | specific private IP address and port. Such a mapping allows incoming | ||
105 | connections to the device with that private address. Some NATs also | ||
106 | implement a default mapping where any inbound traffic that does not | ||
107 | match a mapping will always be forwarded to a specific private | ||
108 | address. Both types of mappings are usually set up manually through | ||
109 | some configuration tool. | ||
110 | |||
111 | Without these manually-created inbound port mappings, clients behind | ||
112 | the NAT would be unable to receive inbound connections, which | ||
113 | represents a loss of connectivity when compared to the original | ||
114 | |||
115 | |||
116 | Expires 14th March 2007 Cheshire, et al. [Page 2] | ||
117 | |||
118 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
119 | |||
120 | |||
121 | Internet architecture [ETEAISD]. For those who view this loss of | ||
122 | connectivity as a bad thing, NAT-PMP allows clients to operate much | ||
123 | more like a host directly connected to the unrestricted public | ||
124 | Internet, with an unrestricted public IP address. NAT-PMP allows | ||
125 | client hosts to communicate with the NAT gateway to request the | ||
126 | creation of inbound mappings on demand. Having created a NAT mapping | ||
127 | to allow inbound connections, the client can then record its public | ||
128 | IP address and public port number in a public registry (e.g. the | ||
129 | world-wide Domain Name System) or otherwise make it accessible to | ||
130 | peers that wish to communicate with it. | ||
131 | |||
132 | |||
133 | 2. Conventions and Terminology Used in this Document | ||
134 | |||
135 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | ||
136 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this | ||
137 | document are to be interpreted as described in "Key words for use in | ||
138 | RFCs to Indicate Requirement Levels" [RFC 2119]. | ||
139 | |||
140 | |||
141 | 3. Protocol and Packet Format | ||
142 | |||
143 | NAT Port Mapping Protocol runs over UDP. Every packet starts with an | ||
144 | 8 bit version followed by an 8 bit operation code. | ||
145 | |||
146 | This document specifies version 0 of the protocol. Any NAT-PMP | ||
147 | gateway implementing this version of the protocol, receiving a | ||
148 | packet with a version number other than 0, MUST return result code 1 | ||
149 | (Unsupported Version). | ||
150 | |||
151 | Opcodes between 0 and 127 are client requests. Opcodes from 128 to | ||
152 | 255 are server responses. Responses always contain a 16 bit result | ||
153 | code in network byte order. A result code of zero indicates success. | ||
154 | Responses also contain a 32 bit unsigned integer corresponding to the | ||
155 | number of seconds since the NAT gateway was rebooted or since its | ||
156 | port mapping state was reset. | ||
157 | |||
158 | This protocol SHOULD only be used when the client determines that | ||
159 | its primary IPv4 address is in one of the private IP address ranges | ||
160 | defined in "Address Allocation for Private Internets" [RFC 1918]. | ||
161 | This includes the address ranges 10/8, 172.16/12, and 192.168/16. | ||
162 | |||
163 | Clients always send their Port Mapping Protocol requests to their | ||
164 | default gateway, as learned via DHCP [RFC 2131], or similar means. | ||
165 | This protocol is designed for small home networks, with a single | ||
166 | logical link (subnet) where the client's default gateway is also the | ||
167 | NAT translator for that network. For more complicated networks where | ||
168 | the NAT translator is some device other than the client's default | ||
169 | gateway, this protocol is not appropriate. | ||
170 | |||
171 | |||
172 | |||
173 | |||
174 | Expires 14th March 2007 Cheshire, et al. [Page 3] | ||
175 | |||
176 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
177 | |||
178 | |||
179 | 3.1 Requests and Responses | ||
180 | |||
181 | NAT gateways are often low-cost devices, with limited memory and | ||
182 | CPU speed. For this reason, to avoid making excessive demands on | ||
183 | the NAT gateway, clients machines SHOULD NOT issue multiple requests | ||
184 | simultaneously in parallel. If a client needs to perform multiple | ||
185 | requests (e.g. on boot, wake from sleep, network connection, etc.) | ||
186 | it SHOULD queue them and issue them serially one at a time. Once the | ||
187 | NAT gateway responds to one request the client machine may issue the | ||
188 | next. In the case of a fast NAT gateway, the client may be able to | ||
189 | complete requests at a rate of hundreds per second. In the case of | ||
190 | a slow NAT gateway that takes perhaps half a second to respond to | ||
191 | a NAT-PMP request, the client SHOULD respect this and allow the | ||
192 | NAT gateway to operate at the pace it can manage, and not overload | ||
193 | it by issuing requests faster than the rate it's answering them. | ||
194 | |||
195 | To determine the puclic IP address or request a port mapping, | ||
196 | a NAT-PMP client sends its request packet to port 5351 of its | ||
197 | configured gateway address, and waits 250ms for a response. If no | ||
198 | NAT-PMP response is received from the gateway after 250ms, the client | ||
199 | retransmits its request and waits 500ms. The client SHOULD repeat | ||
200 | this process with the interval between attempts doubling each time. | ||
201 | If, after sending its 9th attempt (and then waiting for 64 seconds), | ||
202 | the client has still received no response, then it SHOULD conclude | ||
203 | that this gateway does not support NAT Port Mapping Protocol and | ||
204 | MAY log an error message indicating this fact. In addition, if the | ||
205 | NAT-PMP client receives an "ICMP Port Unreachable" message from the | ||
206 | gateway for port 5351 then it can skip any remaining retransmissions | ||
207 | and conclude immediately that the gateway does not support NAT-PMP. | ||
208 | |||
209 | As a performance optimization the client MAY record this information | ||
210 | and use it to suppress further attempts to use NAT-PMP, but the | ||
211 | client should not retain this information for too long. In | ||
212 | particular, any event that may indicate a potential change of gateway | ||
213 | or a change in gateway configuration (hardware link change | ||
214 | indication, change of gateway MAC address, acquisition of new DHCP | ||
215 | lease, receipt of NAT-PMP announcement packet from gateway, etc.) | ||
216 | should cause the client to discard its previous information regarding | ||
217 | the gateway's lack of NAT-PMP support, and send its next NAT-PMP | ||
218 | request packet normally. | ||
219 | |||
220 | |||
221 | 3.2 Determining the Public Address | ||
222 | |||
223 | To determine the public address, the client behind the NAT sends the | ||
224 | following UDP payload to port 5351 of the configured gateway address: | ||
225 | |||
226 | 0 1 | ||
227 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 | ||
228 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
229 | | Vers = 0 | OP = 0 | | ||
230 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
231 | |||
232 | Expires 14th March 2007 Cheshire, et al. [Page 4] | ||
233 | |||
234 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
235 | |||
236 | |||
237 | A compatible NAT gateway MUST generate a response with the following | ||
238 | format: | ||
239 | |||
240 | 0 1 2 3 | ||
241 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
242 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
243 | | Vers = 0 | OP = 128 + 0 | Result Code | | ||
244 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
245 | | Seconds Since Start of Epoch | | ||
246 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
247 | | Public IP Address (a.b.c.d) | | ||
248 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
249 | |||
250 | This response indicates that the NAT gateway implements this version | ||
251 | of the protocol and returns the public IP address of the NAT gateway. | ||
252 | If the result code is non-zero, the value of Public IP Address is | ||
253 | undefined (MUST be set to zero on transmission, and MUST be ignored | ||
254 | on reception). | ||
255 | |||
256 | The NAT gateway MUST fill in the "Seconds Since Start of Epoch" field | ||
257 | with the time elapsed since its port mapping table was initialized on | ||
258 | startup or reset for any other reason (see Section 3.6 "Seconds Since | ||
259 | Start of Epoch"). | ||
260 | |||
261 | Upon receiving the response packet, the client MUST check the source | ||
262 | IP address, and silently discard the packet if the address is not the | ||
263 | address of the gateway to which the request was sent. | ||
264 | |||
265 | |||
266 | 3.2.1 Announcing Address Changes | ||
267 | |||
268 | When the public IP address of the NAT changes, the NAT gateway MUST | ||
269 | send a gratuitous response to the link-local multicast address | ||
270 | 224.0.0.1, port 5351 with the packet format above to notify clients | ||
271 | of the new public IP address. To accommodate packet loss, the | ||
272 | NAT gateway SHOULD multicast 10 address change notifications. | ||
273 | The interval between the first two notifications SHOULD be 250ms, | ||
274 | and the interval between each subsequent notification SHOULD double. | ||
275 | |||
276 | Upon receiving a gratuitous address change announcement packet, | ||
277 | the client MUST check the source IP address, and silently discard | ||
278 | the packet if the address is not the address of the client's | ||
279 | current configured gateway. This is to guard against inadvertent | ||
280 | misconfigurations where there may be more than one NAT gateway | ||
281 | active on the network. | ||
282 | |||
283 | |||
284 | |||
285 | |||
286 | |||
287 | |||
288 | |||
289 | |||
290 | Expires 14th March 2007 Cheshire, et al. [Page 5] | ||
291 | |||
292 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
293 | |||
294 | |||
295 | 3.3 Creating a Mapping | ||
296 | |||
297 | To create a mapping, the client sends a UDP packet to port 5351 | ||
298 | of the gateway's private IP address with the following format: | ||
299 | |||
300 | 0 1 2 3 | ||
301 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
302 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
303 | | Vers = 0 | OP = x | Reserved (MUST be zero) | | ||
304 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
305 | | Private Port | Requested Public Port | | ||
306 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
307 | | Requested Port Mapping Lifetime in Seconds | | ||
308 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
309 | |||
310 | Opcodes supported: | ||
311 | 1 - Map UDP | ||
312 | 2 - Map TCP | ||
313 | |||
314 | The Reserved field MUST be set to zero on transmission and MUST | ||
315 | be ignored on reception. | ||
316 | |||
317 | The Private Port is set to the local port on which the client is | ||
318 | listening. | ||
319 | |||
320 | The Requested Public Port SHOULD usually be set to the same value as | ||
321 | the local Private Port, or zero if the client has no preference for | ||
322 | what port is assigned. However, the gateway is not obliged to assign | ||
323 | the port requested, and may choose not to, either for policy reasons | ||
324 | (e.g. port 80 is reserved and clients may not request it) or because | ||
325 | that port has already been assigned to some other client. Because | ||
326 | of this, some product developers have questioned the value of having | ||
327 | the Requested Public Port field at all. The reason is for failure | ||
328 | recovery. Most low-cost home NAT gateways do not record temporary | ||
329 | port mappings in persistent storage, so if the gateway crashes or is | ||
330 | rebooted, all the mappings are lost. A renewal packet is formatted | ||
331 | identically to an initial mapping request packet, except that for | ||
332 | renewals the client sets the Requested Public Port field to the | ||
333 | port the gateway actually assigned, rather than the port the client | ||
334 | originally wanted. When a freshly-rebooted NAT gateway receives a | ||
335 | renewal packet from a client, it appears to the gateway just like | ||
336 | an ordinary initial request for a port mapping, except that in this | ||
337 | case the Requested Public Port is likely to be one that the NAT | ||
338 | gateway *is* willing to allocate (it allocated it to this client | ||
339 | right before the reboot, so it should presumably be willing to | ||
340 | allocate it again). | ||
341 | |||
342 | The RECOMMENDED Port Mapping Lifetime is 3600 seconds. | ||
343 | |||
344 | |||
345 | |||
346 | |||
347 | |||
348 | Expires 14th March 2007 Cheshire, et al. [Page 6] | ||
349 | |||
350 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
351 | |||
352 | |||
353 | After sending the port mapping request, the client then waits for the | ||
354 | NAT gateway to respond. If after 250ms, the gateway doesn't respond, | ||
355 | the client SHOULD re-issue its request as described above in Section | ||
356 | 3.1 "Requests and Responses". | ||
357 | |||
358 | The NAT gateway responds with the following packet format: | ||
359 | |||
360 | 0 1 2 3 | ||
361 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
362 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
363 | | Vers = 0 | OP = 128 + x | Result Code | | ||
364 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
365 | | Seconds Since Start of Epoch | | ||
366 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
367 | | Private Port | Mapped Public Port | | ||
368 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
369 | | Port Mapping Lifetime in Seconds | | ||
370 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
371 | |||
372 | The 'x' in the OP field MUST match what the client requested. Some | ||
373 | NAT gateways are incapable of creating a UDP port mapping without | ||
374 | also creating a corresponding TCP port mapping, and vice versa, and | ||
375 | these gateways MUST NOT implement NAT Port Mapping Protocol until | ||
376 | this deficiency is fixed. A NAT gateway which implements this | ||
377 | protocol MUST be able to create TCP-only and UDP-only port mappings. | ||
378 | |||
379 | If a NAT gateway silently creates a pair of mappings for a client | ||
380 | that only requested one mapping, then it may expose that client to | ||
381 | receiving inbound UDP packets or inbound TCP connection requests | ||
382 | that it did not ask for and does not want. | ||
383 | |||
384 | While a NAT gateway MUST NOT automatically create mappings for TCP | ||
385 | when the client requests UDP, and vice versa, the NAT gateway MUST | ||
386 | reserve the companion port so the same client can choose to map it | ||
387 | in the future. For example, if a client requests to map TCP port 80, | ||
388 | as long as the client maintains the lease for that TCP port mapping, | ||
389 | another client with a different IP address MUST NOT be able to | ||
390 | successfully acquire the mapping for UDP port 80. | ||
391 | |||
392 | The client normally requests the public port matching the private | ||
393 | port. If that public port is not available, the NAT gateway MUST | ||
394 | return a public port that is available or return an error code if | ||
395 | no ports are available. | ||
396 | |||
397 | The source address of the packet MUST be used for the private address | ||
398 | in the mapping. This protocol is not intended to facilitate one | ||
399 | device behind a NAT creating mappings for other devices. If there | ||
400 | are legacy devices that require inbound mappings, permanent mappings | ||
401 | can be created manually by the administrator, just as they are today. | ||
402 | |||
403 | |||
404 | |||
405 | |||
406 | Expires 14th March 2007 Cheshire, et al. [Page 7] | ||
407 | |||
408 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
409 | |||
410 | |||
411 | If a mapping already exists for a given private port on a given local | ||
412 | client (whether that mapping was created explicitly using NAT-PMP, | ||
413 | implicitly as a result of an outgoing TCP SYN packet, or manually by | ||
414 | a human administrator) and that client requests another mapping for | ||
415 | the same private port (possibly requesting a different public port) | ||
416 | then the mapping request should succeed, returning the already- | ||
417 | assigned public port. This is necessary to handle the case where | ||
418 | a client requests a mapping with requested public port X, and is | ||
419 | granted a mapping with actual public port Y, but the acknowledgement | ||
420 | packet gets lost. When the client retransmits its mapping request, | ||
421 | it should get back the same positive acknowledgement as was sent (and | ||
422 | lost) the first time. | ||
423 | |||
424 | The NAT gateway SHOULD NOT accept mapping requests destined to the | ||
425 | NAT gateway's public IP address or received on its public network | ||
426 | interface. Only packets received on the private interface(s) with | ||
427 | a destination address matching the private address(es) of the NAT | ||
428 | gateway should be allowed. | ||
429 | |||
430 | The NAT gateway MUST fill in the "Seconds Since Start of Epoch" field | ||
431 | with the time elapsed since its port mapping table was initialized on | ||
432 | startup or reset for any other reason (see Section 3.6 "Seconds Since | ||
433 | Start of Epoch"). | ||
434 | |||
435 | The Port Mapping Lifetime is an unsigned integer in seconds. The NAT | ||
436 | gateway MAY reduce the lifetime from what the client requested. The | ||
437 | NAT gateway SHOULD NOT offer a lease lifetime greater than that | ||
438 | requested by the client. | ||
439 | |||
440 | Upon receiving the response packet, the client MUST check the source | ||
441 | IP address, and silently discard the packet if the address is not the | ||
442 | address of the gateway to which the request was sent. | ||
443 | |||
444 | The client SHOULD begin trying to renew the mapping halfway to expiry | ||
445 | time, like DHCP. The renewal packet should look exactly the same as | ||
446 | a request packet, except that the client SHOULD set the requested | ||
447 | public port to what the NAT gateway previously mapped, not what the | ||
448 | client originally requested. As described above, this enables the | ||
449 | gateway to automatically recover its mapping state after a crash or | ||
450 | reboot. | ||
451 | |||
452 | |||
453 | 3.4 Destroying a Mapping | ||
454 | |||
455 | A mapping may be destroyed in a variety of ways. If a client fails | ||
456 | to renew a mapping, then when its lifetime expires the mapping MUST | ||
457 | be automatically deleted. In the common case where the gateway | ||
458 | device is a combined DHCP server and NAT gateway, when a client's | ||
459 | DHCP address lease expires, the gateway device MAY automatically | ||
460 | delete any mappings belonging to that client. Otherwise a new client | ||
461 | being assigned the same IP address could receive unexpected inbound | ||
462 | |||
463 | |||
464 | Expires 14th March 2007 Cheshire, et al. [Page 8] | ||
465 | |||
466 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
467 | |||
468 | |||
469 | UDP packets or inbound TCP connection requests that it did not ask | ||
470 | for and does not want. | ||
471 | |||
472 | A client MAY also send an explicit packet to request deletion of a | ||
473 | mapping that is no longer needed. A client requests explicit | ||
474 | deletion of a mapping by sending a message to the NAT gateway | ||
475 | requesting the mapping, with the Requested Lifetime in Seconds set | ||
476 | to 0. The requested public port MUST be set to zero by the client | ||
477 | on sending, and MUST be ignored by the gateway on reception. | ||
478 | |||
479 | When a mapping is destroyed successfully as a result of the client | ||
480 | explicitly requesting the deletion, the NAT gateway MUST send a | ||
481 | response packet which is formatted as defined in section 3.3 | ||
482 | "Creating a Mapping". The response MUST contain a result code of 0, | ||
483 | the private port as indicated in the deletion request, a public port | ||
484 | of 0, and a lifetime of 0. The NAT gateway MUST respond to a request | ||
485 | to destroy a mapping that does not exist as if the request were | ||
486 | successful. This is because of the case where the acknowledgement is | ||
487 | lost, and the client retransmits its request to delete the mapping. | ||
488 | In this case the second request to delete the mapping MUST return the | ||
489 | same response packet as the first request. | ||
490 | |||
491 | If the deletion request was unsuccessful, the response MUST contain a | ||
492 | non-zero result code and the requested mapping; the lifetime is | ||
493 | undefined (MUST be set to zero on transmission, and MUST be ignored | ||
494 | on reception). If the client attempts to delete a port mapping which | ||
495 | was manually assigned by some kind of configuration tool, the NAT | ||
496 | gateway MUST respond with a 'Not Authorized' error, result code 2. | ||
497 | |||
498 | When a mapping is destroyed as a result of its lifetime expiring or | ||
499 | for any other reason, if the NAT gateway's internal state indicates | ||
500 | that there are still active TCP connections traversing that now- | ||
501 | defunct mapping, then the NAT gateway SHOULD send appropriately- | ||
502 | constructed TCP RST (reset) packets both to the local client and to | ||
503 | the remote peer on the Internet to terminate that TCP connection. | ||
504 | |||
505 | A client can request the explicit deletion of all its UDP or TCP | ||
506 | mappings by sending the same deletion request to the NAT gateway | ||
507 | with public port, private port, and lifetime set to 0. A client MAY | ||
508 | choose to do this when it first acquires a new IP address in order to | ||
509 | protect itself from port mappings that were performed by a previous | ||
510 | owner of the IP address. After receiving such a deletion request, | ||
511 | the gateway MUST delete all its UDP or TCP port mappings (depending | ||
512 | on the opcode). The gateway responds to such a deletion request with | ||
513 | a response as described above, with the private port set to zero. If | ||
514 | the gateway is unable to delete a port mapping, for example, because | ||
515 | the mapping was manually configured by the administrator, the gateway | ||
516 | MUST still delete as many port mappings as possible, but respond with | ||
517 | a non-zero result code. The exact result code to return depends on | ||
518 | the cause of the failure. If the gateway is able to successfully | ||
519 | delete all port mappings as requested, it MUST respond with a result | ||
520 | code of 0. | ||
521 | |||
522 | Expires 14th March 2007 Cheshire, et al. [Page 9] | ||
523 | |||
524 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
525 | |||
526 | |||
527 | 3.5 Result Codes | ||
528 | |||
529 | Currently defined result codes: | ||
530 | 0 - Success | ||
531 | 1 - Unsupported Version | ||
532 | 2 - Not Authorized/Refused | ||
533 | (e.g. box supports mapping, but user has turned feature off) | ||
534 | 3 - Network Failure | ||
535 | (e.g. NAT box itself has not obtained a DHCP lease) | ||
536 | 4 - Out of resources | ||
537 | (NAT box cannot create any more mappings at this time) | ||
538 | 5 - Unsupported opcode | ||
539 | |||
540 | If the result code is non-zero, the format of the packet following | ||
541 | the result code may be truncated. For example, if the client sends a | ||
542 | request to the server with an opcode of 17 and the server does not | ||
543 | recognize that opcode, the server SHOULD respond with a message where | ||
544 | the opcode is 17 + 128 and the result code is 5 (opcode not | ||
545 | supported). Since the server does not understand the format of | ||
546 | opcode 17, it may not know what to place after the result code. In | ||
547 | some cases, relevant data may follow the opcode to identify the | ||
548 | operation that failed. For example, a client may request a mapping | ||
549 | but that mapping may fail due to resource exhaustion. The server | ||
550 | SHOULD respond with the result code to indicate resource exhaustion | ||
551 | (4) followed by the requested port mapping so the client may identify | ||
552 | which operation failed. | ||
553 | |||
554 | Clients MUST be able to properly handle result codes not defined in | ||
555 | this document. Undefined results codes MUST be treated as fatal | ||
556 | errors of the request. | ||
557 | |||
558 | |||
559 | 3.6 Seconds Since Start of Epoch | ||
560 | |||
561 | Every packet sent by the NAT gateway includes a "Seconds since start | ||
562 | of epoch" field (SSSOE). If the NAT gateway resets or loses the | ||
563 | state of its port mapping table, due to reboot, power failure, or any | ||
564 | other reason, it MUST reset its epoch time and begin counting SSSOE | ||
565 | from 0 again. Whenever a client receives any packet from the NAT | ||
566 | gateway, either gratuitously or in response to a client request, the | ||
567 | client computes its own conservative estimate of the expected SSSOE | ||
568 | value by taking the SSSOE value in the last packet it received from | ||
569 | the gateway and adding 7/8 (87.5%) of the time elapsed since that | ||
570 | packet was received. If the SSSOE in the newly received packet is | ||
571 | less than the client's conservative estimate by more than one second, | ||
572 | then the client concludes that the NAT gateway has undergone a reboot | ||
573 | or other loss of port mapping state, and the client MUST immediately | ||
574 | renew all its active port mapping leases as described in Section 3.7 | ||
575 | "Recreating Mappings On NAT Gateway Reboot". | ||
576 | |||
577 | |||
578 | |||
579 | |||
580 | Expires 14th March 2007 Cheshire, et al. [Page 10] | ||
581 | |||
582 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
583 | |||
584 | |||
585 | 3.7 Recreating Mappings On NAT Gateway Reboot | ||
586 | |||
587 | The NAT gateway MAY store mappings in persistent storage so when it | ||
588 | is powered off or rebooted, it remembers the port mapping state of | ||
589 | the network. | ||
590 | |||
591 | However, maintaining this state is not essential for correct | ||
592 | operation. When the NAT gateway powers on or clears its port mapping | ||
593 | state as the result of a configuration change, it MUST reset the | ||
594 | epoch time and re-announce its IP address as described in Section | ||
595 | 3.2.1 "Announcing Address Changes". Reception of this packet where | ||
596 | time has apparently gone backwards serves as a hint to clients | ||
597 | on the network that they SHOULD immediately send renewal packets | ||
598 | (to immediately recreate their mappings) instead of waiting until | ||
599 | the originally scheduled time for those renewals. Clients who miss | ||
600 | receiving those gateway announcement packets for any reason will | ||
601 | still renew their mappings at the originally scheduled time and cause | ||
602 | their mappings to be recreated; it will just take a little longer for | ||
603 | these clients. | ||
604 | |||
605 | A mapping renewal packet is formatted identically to an original | ||
606 | mapping request; from the point of view of the client it is a | ||
607 | renewal of an existing mapping, but from the point of view of the | ||
608 | freshly-rebooted NAT gateway it appears as a new mapping request. | ||
609 | |||
610 | This self-healing property of the protocol is very important. | ||
611 | |||
612 | The remarkable reliability of the Internet as a whole derives | ||
613 | in large part from the fact that important state is held in the | ||
614 | endpoints, not in the network itself [ETEAISD]. Power-cycling an | ||
615 | Ethernet switch results only in a brief interruption in the flow | ||
616 | of packets; established TCP connections through that switch are not | ||
617 | broken, merely delayed for a few seconds. Indeed, an old Ethernet | ||
618 | switch can even be replaced with a new one, and as long as the cables | ||
619 | are transferred over reasonably quickly, after the upgrade all the | ||
620 | TCP connections that were previously going though the old switch will | ||
621 | be unbroken and now going through the new one. The same is true of | ||
622 | IP routers, wireless base stations, etc. The one exception is NAT | ||
623 | gateways. Because the port mapping state is required for the NAT | ||
624 | gateway to know where to forward inbound packets, loss of that state | ||
625 | breaks connectivity through the NAT gateway. By allowing clients to | ||
626 | detect when this loss of NAT gateway state has occurred, and recreate | ||
627 | it on demand, we turn hard state in the network into soft state, and | ||
628 | allow it to be recovered automatically when needed. | ||
629 | |||
630 | Without this automatic recreation of soft state in the NAT gateway, | ||
631 | reliable long-term networking would not be achieved. As mentioned | ||
632 | above, the reliability of the Internet does not come from trying | ||
633 | to build a perfect network in which errors never happen, but from | ||
634 | accepting that in any sufficiently large system there will always be | ||
635 | some component somewhere that's failing, and designing mechanisms | ||
636 | |||
637 | |||
638 | Expires 14th March 2007 Cheshire, et al. [Page 11] | ||
639 | |||
640 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
641 | |||
642 | |||
643 | that can handle those failures and recover. To illustrate this point | ||
644 | with an example, consider the following scenario: Imagine a network | ||
645 | security camera that has a web interface and accepts incoming | ||
646 | connections from web browser clients. Imagine this network security | ||
647 | camera uses NAT-PMP or a similar protocol to set up an inbound | ||
648 | port mapping in the NAT gateway so that it can receive incoming | ||
649 | connections from clients the other side of the NAT gateway. | ||
650 | Now, this camera may well operate for weeks, months, or even years. | ||
651 | During that time it's possible that the NAT gateway could experience | ||
652 | a power failure or be rebooted. The user could upgrade the NAT | ||
653 | gateway's firmware, or even replace the entire NAT gateway device | ||
654 | with a newer model. The general point is that if the camera operates | ||
655 | for a long enough period of time, some kind of disruption to the NAT | ||
656 | gateway becomes inevitable. The question is not whether the NAT | ||
657 | gateway will lose its port mappings, but when, and how often. | ||
658 | If the network camera and devices like it on the network can detect | ||
659 | when the NAT gateway has lost its port mappings, and recreate them | ||
660 | automatically, then these disruptions are self-correcting and | ||
661 | invisible to the end user. If, on the other hand, the disruptions are | ||
662 | not self-correcting, and after a NAT gateway reboot the user has to | ||
663 | manually reset or reboot all the other devices on the network too, | ||
664 | then these disruptions are *very* visible to the end user. This | ||
665 | aspect of the design is what makes the difference between a protocol | ||
666 | that keeps on working indefinitely over a time scale of months or | ||
667 | years, and a protocol that works in brief testing, but in the real | ||
668 | world is continually failing and requiring manual intervention to get | ||
669 | it going again. | ||
670 | |||
671 | When a client renews its port mappings as the result of receiving | ||
672 | a packet where the "Seconds since start of epoch" field (SSSOE) | ||
673 | indicates that a reboot or similar loss of state has occurred, | ||
674 | the client MUST first delay by a random amount of time selected | ||
675 | with uniform random distribution in the range 0 to 5 seconds, and | ||
676 | then send its first port mapping request. After that request is | ||
677 | acknowledged by the gateway, the client may then send its second | ||
678 | request, and so on, as rapidly as the gateway allows. The requests | ||
679 | SHOULD be issued serially, one at a time; the client SHOULD NOT issue | ||
680 | multiple requests simultaneously in parallel. | ||
681 | |||
682 | The discussion in this section focusses on recreating inbound port | ||
683 | mappings after loss of NAT gateway state, because that is the more | ||
684 | serious problem. Losing port mappings for outgoing connections | ||
685 | destroys those currently active connections, but does not prevent | ||
686 | clients from establishing new outgoing connections. In contrast, | ||
687 | losing inbound port mappings not only destroys all existing inbound | ||
688 | connections, but also prevents the reception of any new inbound | ||
689 | connections until the port mapping is recreated. Accordingly, | ||
690 | we consider recovery of inbound port mappings the more important | ||
691 | priority. However, clients that want outgoing connections to survive | ||
692 | a NAT gateway reboot can also achieve that using NAT-PMP. After | ||
693 | initiating an outbound TCP connection (which will cause the NAT | ||
694 | |||
695 | |||
696 | Expires 14th March 2007 Cheshire, et al. [Page 12] | ||
697 | |||
698 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
699 | |||
700 | |||
701 | gateway to establish an implicit port mapping) the client should send | ||
702 | the NAT gateway a port mapping request for the source port of its TCP | ||
703 | connection, which will cause the NAT gateway to send a response | ||
704 | giving the public port it allocated for that mapping. The client can | ||
705 | then store this information, and use later to recreate the mapping | ||
706 | if it determines that the NAT gateway has lost its mapping state. | ||
707 | |||
708 | |||
709 | 3.8 NAT Gateways with NAT Function Disabled | ||
710 | |||
711 | Note that *only* devices currently acting in the role of NAT gateway | ||
712 | should participate in NAT-PMP protocol exchanges with clients. | ||
713 | A network device that is capable of NAT (and NAT-PMP), but is | ||
714 | currently configured not to perform that function, (e.g. it is | ||
715 | acting as a traditional IP router, forwarding packets without | ||
716 | modifying them), MUST NOT respond to NAT-PMP requests from clients, | ||
717 | or send spontaneous NAT-PMP address-change announcements. | ||
718 | |||
719 | In particular, a network device not currently acting in the role of | ||
720 | NAT gateway should not even respond to NAT-PMP requests by returning | ||
721 | an error code such as "2 - Not Authorized/Refused", since to do so | ||
722 | is misleading to clients -- it suggests that NAT port mapping is | ||
723 | necessary on this network for the client to successfully receive | ||
724 | inbound connections, but is not available because the administrator | ||
725 | has chosen to disable that functionality. | ||
726 | |||
727 | Clients should also be careful to avoid making unfounded assumptions, | ||
728 | such as the assumption that if the client has an IPv4 address in | ||
729 | one of the RFC 1918 private IP address ranges then that means | ||
730 | NAT necessarily must be in use. Net 10/8 has enough addresses | ||
731 | to build a private network with millions of hosts and thousands | ||
732 | of interconnected subnets, all without any use of NAT. Many | ||
733 | organizations have built such private networks that benefit from | ||
734 | using standard TCP/IP technology, but by choice do not connect | ||
735 | to the public Internet. The purpose of NAT-PMP is to mitigate some | ||
736 | of the damage caused by NAT. It would be an ironic and unwanted | ||
737 | side-effect of this protocol if it were to lead well-meaning but | ||
738 | misguided developers to create products that refuse to work on a | ||
739 | private network *unless* they can find a NAT gateway to talk to. | ||
740 | Consequently, a client finding that NAT-PMP is not available on its | ||
741 | network should not give up, but should proceed on the assumption | ||
742 | that the network may be a traditional routed IP network, with no | ||
743 | address translation being used. This assumption may not always be | ||
744 | true, but it is better than the alternative of falsely assuming | ||
745 | the worst and not even trying to use normal (non-NAT) IP networking. | ||
746 | |||
747 | If a network device not currently acting in the role of NAT gateway | ||
748 | receives UDP packets addressed to port 5351, it SHOULD respond | ||
749 | immediately with an "ICMP Port Unreachable" message to tell the | ||
750 | client that it needn't continue with timeouts and retransmissions, | ||
751 | and it should assume that NAT-PMP is not available and not needed | ||
752 | on this network. | ||
753 | |||
754 | Expires 14th March 2007 Cheshire, et al. [Page 13] | ||
755 | |||
756 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
757 | |||
758 | |||
759 | 4. UNSAF Considerations | ||
760 | |||
761 | The document "IAB Considerations for UNSAF Across NAT" [RFC 3424] | ||
762 | covers a number of issues when working with NATs. RFC 3424 outlines | ||
763 | some requirements for any document that attempts to work around | ||
764 | problems associated with NATs. This section addresses those | ||
765 | requirements. | ||
766 | |||
767 | |||
768 | 4.1 Scope | ||
769 | |||
770 | This protocol addresses the needs of TCP and UDP transport peers that | ||
771 | are separated from the public internet by exactly one NAT. Such | ||
772 | peers must have access to some form of directory server for | ||
773 | registering the public IP address and port at which they can be | ||
774 | reached. | ||
775 | |||
776 | |||
777 | 4.2 Transition Plan | ||
778 | |||
779 | Any client making use of this protocol SHOULD implement IPv6 support. | ||
780 | If a client supports IPv6 and is running on a device with a global | ||
781 | IPv6 address, that IPv6 address SHOULD be preferred to the IPv4 | ||
782 | public address using this NAT mapping protocol. In case other | ||
783 | clients do not have IPv6 connectivity, both the IPv4 and IPv6 | ||
784 | addresses SHOULD be registered with whatever form of directory server | ||
785 | is used. Preference SHOULD be given to IPv6 addresses when | ||
786 | available. By implementing support for IPv6 and using this protocol | ||
787 | for IPv4, vendors can ship products today that will work under both | ||
788 | scenarios. As IPv6 is more widely deployed, clients of this protocol | ||
789 | following these recommendations will transparently make use of IPv6. | ||
790 | |||
791 | |||
792 | 4.3 Failure Cases | ||
793 | |||
794 | Aside from NATs that do not implement this protocol, there are a | ||
795 | number of situations where this protocol may not work. | ||
796 | |||
797 | |||
798 | 4.3.1 NAT Behind NAT | ||
799 | |||
800 | Some people's primary IP address, assigned by their ISP, may itself | ||
801 | be a NAT address. In addition, some people may have a public IP | ||
802 | address, but may then double NAT themselves, perhaps by choice or | ||
803 | perhaps by accident. Although it might be possible in principle for | ||
804 | one NAT gateway to recursively request a mapping from the next one, | ||
805 | this document does not advocate that and does not try to prescribe | ||
806 | how it would be done. | ||
807 | |||
808 | It would be a lot of work to implement nested NAT port mapping | ||
809 | correctly, and there are a number of reasons why the end result might | ||
810 | |||
811 | |||
812 | Expires 14th March 2007 Cheshire, et al. [Page 14] | ||
813 | |||
814 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
815 | |||
816 | |||
817 | not be as useful as we might hope. Consider the case of an ISP that | ||
818 | offers each of its customers only a single NAT address. This ISP | ||
819 | could instead have chosen to provide each customer with a single | ||
820 | public IP address, or, if the ISP insists on running NAT, it could | ||
821 | have chosen to allow each customer a reasonable number of addresses, | ||
822 | enough for each customer device to have its own NAT address directly | ||
823 | from the ISP. If instead this ISP chooses to allow each customer | ||
824 | just one and only one NAT address, forcing said customer to run | ||
825 | nested NAT in order to use more than one device, it seems unlikely | ||
826 | that such an ISP would be so obliging as to provide a NAT service | ||
827 | that supports NAT Port Mapping Protocol. Supposing that such an ISP | ||
828 | did wish to offer its customers NAT service with NAT-PMP so as to | ||
829 | give them the ability to receive inbound connections, this ISP could | ||
830 | easily choose to allow each client to request a reasonable number of | ||
831 | DHCP addresses from that gateway. Remember that Net 10/8 [RFC 1918] | ||
832 | allows for over 16 million addresses, so NAT addresses are not in any | ||
833 | way in short supply. A single NAT gateway with 16 million available | ||
834 | addresses is likely to run out of packet forwarding capacity before | ||
835 | it runs out of private addresses to hand out. In this way the ISP | ||
836 | could offer single-level NAT with NAT-PMP, obviating the need to | ||
837 | support nested NAT-PMP. In addition, an ISP that is motivated to | ||
838 | provide their customers with unhindered access to the Internet by | ||
839 | allowing incoming as well as outgoing connections has better ways | ||
840 | to offer this service. Such an ISP could offer its customers real | ||
841 | public IP addresses instead of NAT addresses, or could even choose | ||
842 | to offer its customers full IPv6 connectivity, where no mapping or | ||
843 | translation is required at all. | ||
844 | |||
845 | |||
846 | 4.3.2 NATs with Multiple Public IP Addresses | ||
847 | |||
848 | If a NAT maps private addresses to multiple public addresses, | ||
849 | then it SHOULD pick one of those addresses as the one it will | ||
850 | support for inbound connections, and for the purposes of this | ||
851 | protocol it SHOULD act as if that address were its only address. | ||
852 | |||
853 | |||
854 | 4.3.3 NATs and Routed Private Networks | ||
855 | |||
856 | In some cases, a large network may be subnetted. Some sites | ||
857 | may install a NAT gateway and subnet the private network. | ||
858 | Such subnetting breaks this protocol because the router address | ||
859 | is not necessarily the address of the device performing NAT. | ||
860 | |||
861 | Addressing this problem is not a high priority. Any site with the | ||
862 | resources to set up such a configuration should have the resources to | ||
863 | add manual mappings or attain a range of globally unique addresses. | ||
864 | |||
865 | Not all NATs will support this protocol. In the case where a client | ||
866 | is run behind a NAT that does not support this protocol, the software | ||
867 | relying on the functionality of this protocol may be unusable. | ||
868 | |||
869 | |||
870 | Expires 14th March 2007 Cheshire, et al. [Page 15] | ||
871 | |||
872 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
873 | |||
874 | |||
875 | 4.3.4 Communication Between Hosts Behind the Same NAT | ||
876 | |||
877 | NAT gateways supporting NAT-PMP should also implement "hairpin | ||
878 | translation". Hairpin translation means supporting communication | ||
879 | between two local clients being served by the same NAT gateway. | ||
880 | |||
881 | Suppose device A is listening on private address and port 10.0.0.2:80 | ||
882 | for incoming connections. Using NAT-PMP, device A has obtained a | ||
883 | mapping to public address and port x.x.x.x:80, and has recorded this | ||
884 | public address and port in a public directory of some kind. For | ||
885 | example, it could have created a DNS SRV record containing this | ||
886 | information, and recorded it, using DNS Dynamic Update [RFC 3007], in | ||
887 | a publicly accessible DNS server. Suppose then that device B, behind | ||
888 | the same NAT gateway as device A, but unknowing or uncaring of this | ||
889 | fact, retrieves device A's DNS SRV record and attempts to open a TCP | ||
890 | connection to x.x.x.x:80. The outgoing packets addressed to this | ||
891 | public Internet address will be sent to the NAT gateway for | ||
892 | translation and forwarding. Having translated the source address and | ||
893 | port number on the outgoing packet, the NAT gateway needs to be smart | ||
894 | enough to recognize that the destination address is in fact itself, | ||
895 | and then feed this packet back into its packet reception engine, to | ||
896 | perform the destination port mapping lookup to translate and forward | ||
897 | this packet to device A at address and port 10.0.0.2:80. | ||
898 | |||
899 | 4.3.5 Non UDP/TCP Transport Traffic | ||
900 | |||
901 | Any communication over transport protocols other than TCP and UDP | ||
902 | will not be served by this protocol. Examples are Generic Routing | ||
903 | Encapsulation (GRE), Authentication Header (AH) and Encapsulating | ||
904 | Security Payload (ESP). | ||
905 | |||
906 | 4.4 Long Term Solution | ||
907 | |||
908 | As IPv6 is deployed, clients of this protocol supporting IPv6 will be | ||
909 | able to bypass this protocol and the NAT when communicating with | ||
910 | other IPv6 devices. In order to ensure this transition, any client | ||
911 | implementing this protocol SHOULD also implement IPv6 and use this | ||
912 | solution only when IPv6 is not available to both peers. | ||
913 | |||
914 | 4.5 Existing Deployed NATs | ||
915 | |||
916 | Existing deployed NATs will not support this protocol. This protocol | ||
917 | will only work with NATs that are upgraded to support it. | ||
918 | |||
919 | |||
920 | |||
921 | |||
922 | |||
923 | |||
924 | |||
925 | |||
926 | |||
927 | |||
928 | Expires 14th March 2007 Cheshire, et al. [Page 16] | ||
929 | |||
930 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
931 | |||
932 | |||
933 | 5. Security Considerations | ||
934 | |||
935 | As discussed in section 3.2 "Determining the Public Address", only | ||
936 | clients on the private side of the NAT may create port mappings, and | ||
937 | only on behalf of themselves. By using IP address spoofing, it's | ||
938 | possible for one client to delete the port mappings of another | ||
939 | client. It's also possible for one client to create port mappings on | ||
940 | behalf of another client. The best way to deal with this | ||
941 | vulnerability is to use IPSec [RFC 2401]. | ||
942 | |||
943 | Since allowing incoming connections is often a policy decision, any | ||
944 | NAT gateway implementing this protocol SHOULD have an administrative | ||
945 | mechanism to disable it. | ||
946 | |||
947 | Some people view the property that NATs block inbound connections as | ||
948 | a security benefit which is undermined by this protocol. The authors | ||
949 | of this document have a different point of view. In the days before | ||
950 | NAT, all hosts had unique public IP addresses, and had unhindered | ||
951 | ability to communicate with any other host on the Internet. When NAT | ||
952 | came along it broke this unhindered connectivity, relegating many | ||
953 | hosts to second-class status, unable to receive inbound connections. | ||
954 | This protocol goes some way to undo some of that damage. The purpose | ||
955 | of a NAT gateway should be to allow several hosts to share a single | ||
956 | address, not to simultaneously impede those host's ability to | ||
957 | communicate freely. Security is most properly provided by end-to-end | ||
958 | cryptographic security, and/or by explicit firewall functionality, as | ||
959 | appropriate. Blocking of certain connections should occur only as a | ||
960 | result of explicit and intentional firewall policy, not as an | ||
961 | accidental side-effect of some other technology. | ||
962 | |||
963 | |||
964 | 6. IANA Considerations | ||
965 | |||
966 | No IANA services are required by this document. | ||
967 | |||
968 | |||
969 | 7. Acknowledgments | ||
970 | |||
971 | The concepts described in this document have been explored, developed | ||
972 | and implemented with help from Bob Bradley, Josh Graessley, Rob | ||
973 | Newberry, Roger Pantos, John Saxton, and James Woodyatt. | ||
974 | |||
975 | |||
976 | 8. Deployment History | ||
977 | |||
978 | NAT-PMP client software first became available to the public | ||
979 | through Apple's Darwin Open Source code in August 2004. | ||
980 | NAT-PMP implementations began shipping to end users in large | ||
981 | volumes (i.e. millions) with the launch of Mac OS X 10.4 Tiger | ||
982 | and Bonjour for Windows 1.0 in April 2005. | ||
983 | |||
984 | |||
985 | |||
986 | Expires 14th March 2007 Cheshire, et al. [Page 17] | ||
987 | |||
988 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
989 | |||
990 | |||
991 | The NAT-PMP client in Mac OS X 10.4 Tiger and Bonjour for Windows | ||
992 | exists as part of the mDNSResponder system service. When a client | ||
993 | advertises a service using Wide Area Bonjour [DNS-SD], and the | ||
994 | machine is behind a NAT-PMP-capable NAT gateway, then if the machine | ||
995 | is so configured, the mDNSResponder system service automatically uses | ||
996 | NAT-PMP to set up an inbound port mapping, and then records the | ||
997 | public IP address and port in the global DNS. Existing client | ||
998 | software using the existing Bonjour programming APIs [Bonjour] | ||
999 | gets this functionality automatically. The logic is that if client | ||
1000 | software publishes its information into the global DNS via Wide Area | ||
1001 | Bonjour service advertising, then it's reasonable to infer an | ||
1002 | expectation that this information should be usable by the peers | ||
1003 | retrieving it. Generally speaking, recording a private IP address | ||
1004 | like 10.0.0.2 in the public DNS is completely pointless because that | ||
1005 | address is not reachable from clients on the other side of the NAT | ||
1006 | gateway. In the case of a home user with a single computer directly | ||
1007 | connected to their Cable or DSL modem, with a single global IPv4 | ||
1008 | address and no NAT gateway (a surprisingly common configuration), | ||
1009 | publishing that IP address into the global DNS is useful because that | ||
1010 | IP address is reachable. In contrast, a home user using a NAT gateway | ||
1011 | to share a single global IPv4 address between several computers loses | ||
1012 | this ability to receive inbound connections easily. This breaks many | ||
1013 | peer-to-peer collaborative applications, like the multi-user text | ||
1014 | editor SubEthaEdit [SEE]. Automatically creating the necessary | ||
1015 | inbound port mappings helps remedy this unintended side-effect of | ||
1016 | NAT. | ||
1017 | |||
1018 | The server side of the NAT-PMP protocol is implemented in Apple's | ||
1019 | "AirPort Extreme" and "AirPort Express" wireless base stations. | ||
1020 | |||
1021 | |||
1022 | 9. Copyright Notice | ||
1023 | |||
1024 | Copyright (C) The Internet Society (2006). | ||
1025 | |||
1026 | This document is subject to the rights, licenses and restrictions | ||
1027 | contained in BCP 78, and except as set forth therein, the authors | ||
1028 | retain all their rights. For the purposes of this document, | ||
1029 | the term "BCP 78" refers exclusively to RFC 3978, "IETF Rights | ||
1030 | in Contributions", published March 2005. | ||
1031 | |||
1032 | This document and the information contained herein are provided on | ||
1033 | an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE | ||
1034 | REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE | ||
1035 | INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR | ||
1036 | IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF | ||
1037 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED | ||
1038 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. | ||
1039 | |||
1040 | |||
1041 | |||
1042 | |||
1043 | |||
1044 | Expires 14th March 2007 Cheshire, et al. [Page 18] | ||
1045 | |||
1046 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
1047 | |||
1048 | |||
1049 | 10. Normative References | ||
1050 | |||
1051 | [RFC 1918] Y. Rekhter et.al., "Address Allocation for Private | ||
1052 | Internets", RFC 1918, February 1996. | ||
1053 | |||
1054 | [RFC 2119] RFC 2119 - Key words for use in RFCs to Indicate | ||
1055 | Requirement Levels | ||
1056 | |||
1057 | |||
1058 | 11. Informative References | ||
1059 | |||
1060 | [Bonjour] Apple "Bonjour" <http://developer.apple.com/bonjour/> | ||
1061 | |||
1062 | [ETEAISD] J. Saltzer, D. Reed and D. Clark: "End-to-end arguments in | ||
1063 | system design", ACM Trans. Comp. Sys., 2(4):277-88, Nov. | ||
1064 | 1984 | ||
1065 | |||
1066 | [DNS-SD] Cheshire, S., and M. Krochmal, "DNS-Based Service | ||
1067 | Discovery", Internet-Draft (work in progress), | ||
1068 | draft-cheshire-dnsext-dns-sd-04.txt, August 2006. | ||
1069 | |||
1070 | [mDNS] Cheshire, S., and M. Krochmal, "Multicast DNS", | ||
1071 | Internet-Draft (work in progress), | ||
1072 | draft-cheshire-dnsext-multicastdns-06.txt, August 2006. | ||
1073 | |||
1074 | [RFC 2131] R. Droms, "Dynamic Host Configuration Protocol", RFC 2131, | ||
1075 | March 1997. | ||
1076 | |||
1077 | [RFC 2401] Atkinson, R. and S. Kent, "Security Architecture for the | ||
1078 | Internet Protocol", RFC 2401, November 1998. | ||
1079 | |||
1080 | [RFC 2663] Srisuresh, P. and M. Holdrege, "IP Network Address | ||
1081 | Translator (NAT) Terminology and Considerations", RFC | ||
1082 | 2663, August 1999. | ||
1083 | |||
1084 | [RFC 3007] Wellington, B., "Simple Secure Domain Name System | ||
1085 | (DNS) Dynamic Update", RFC 3007, November 2000. | ||
1086 | |||
1087 | [SEE] <http://www.codingmonkeys.de/subethaedit/> | ||
1088 | |||
1089 | [RFC 3022] RFC 3022 - Network Address Translator | ||
1090 | |||
1091 | [RFC 3424] RFC 3424 - IAB Considerations for UNilateral Self-Address | ||
1092 | Fixing (UNSAF) Across Network Address Translation | ||
1093 | |||
1094 | |||
1095 | |||
1096 | |||
1097 | |||
1098 | |||
1099 | |||
1100 | |||
1101 | |||
1102 | Expires 14th March 2007 Cheshire, et al. [Page 19] | ||
1103 | |||
1104 | Internet Draft NAT Port Mapping Protocol 14th September 2006 | ||
1105 | |||
1106 | |||
1107 | 12. Authors' Addresses | ||
1108 | |||
1109 | Stuart Cheshire | ||
1110 | Apple Computer, Inc. | ||
1111 | 1 Infinite Loop | ||
1112 | Cupertino | ||
1113 | California 95014 | ||
1114 | USA | ||
1115 | |||
1116 | Phone: +1 408 974 3207 | ||
1117 | EMail: rfc [at] stuartcheshire [dot] org | ||
1118 | |||
1119 | |||
1120 | Marc Krochmal | ||
1121 | Apple Computer, Inc. | ||
1122 | 1 Infinite Loop | ||
1123 | Cupertino | ||
1124 | California 95014 | ||
1125 | USA | ||
1126 | |||
1127 | Phone: +1 408 974 4368 | ||
1128 | EMail: marc [at] apple [dot] com | ||
1129 | |||
1130 | |||
1131 | Kiren Sekar | ||
1132 | Sharpcast, Inc. | ||
1133 | 250 Cambridge Ave, Suite 101 | ||
1134 | Palo Alto | ||
1135 | California 94306 | ||
1136 | USA | ||
1137 | |||
1138 | Phone: +1 650 323 1960 | ||
1139 | EMail: ksekar [at] sharpcast [dot] com | ||
1140 | |||
1141 | |||
1142 | |||
1143 | |||
1144 | |||
1145 | |||
1146 | |||
1147 | |||
1148 | |||
1149 | |||
1150 | |||
1151 | |||
1152 | |||
1153 | |||
1154 | |||
1155 | |||
1156 | |||
1157 | |||
1158 | |||
1159 | |||
1160 | Expires 14th March 2007 Cheshire, et al. [Page 20] | ||
diff --git a/src/upnp/test_upnp.c b/src/upnp/test_upnp.c new file mode 100644 index 000000000..628b40d7c --- /dev/null +++ b/src/upnp/test_upnp.c | |||
@@ -0,0 +1,110 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2006 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | /** | ||
21 | * @file src/transports/upnp/upnptest.c | ||
22 | * @brief Testcase for UPnP | ||
23 | * @author Christian Grothoff | ||
24 | */ | ||
25 | |||
26 | #include "gnunet_util.h" | ||
27 | #include "gnunet_upnp_service.h" | ||
28 | #include "gnunet_core.h" | ||
29 | #include "platform.h" | ||
30 | |||
31 | |||
32 | |||
33 | int | ||
34 | main (int argc, const char *argv[]) | ||
35 | { | ||
36 | static GNUNET_CoreAPIForPlugins capi; | ||
37 | struct GNUNET_GE_Context *ectx; | ||
38 | struct GNUNET_GC_Configuration *cfg; | ||
39 | struct in_addr addr; | ||
40 | int i; | ||
41 | GNUNET_UPnP_ServiceAPI *upnp; | ||
42 | struct GNUNET_PluginHandle *plug; | ||
43 | GNUNET_ServicePluginInitializationMethod init; | ||
44 | GNUNET_ServicePluginShutdownMethod done; | ||
45 | char ntop_buf[INET_ADDRSTRLEN]; | ||
46 | |||
47 | ectx = GNUNET_GE_create_context_stderr (GNUNET_NO, | ||
48 | GNUNET_GE_WARNING | GNUNET_GE_ERROR | ||
49 | | GNUNET_GE_FATAL | GNUNET_GE_USER | | ||
50 | GNUNET_GE_ADMIN | | ||
51 | GNUNET_GE_DEVELOPER | | ||
52 | GNUNET_GE_IMMEDIATE | | ||
53 | GNUNET_GE_BULK); | ||
54 | GNUNET_GE_setDefaultContext (ectx); | ||
55 | cfg = GNUNET_GC_create (); | ||
56 | GNUNET_GE_ASSERT (ectx, cfg != NULL); | ||
57 | GNUNET_os_init (ectx); | ||
58 | capi.ectx = ectx; | ||
59 | capi.cfg = cfg; | ||
60 | plug = GNUNET_plugin_load (ectx, "libgnunet", "module_upnp"); | ||
61 | if (plug == NULL) | ||
62 | { | ||
63 | GNUNET_GC_free (cfg); | ||
64 | GNUNET_GE_free_context (ectx); | ||
65 | return 1; | ||
66 | } | ||
67 | init = GNUNET_plugin_resolve_function (plug, "provide_", GNUNET_YES); | ||
68 | if (init == NULL) | ||
69 | { | ||
70 | GNUNET_plugin_unload (plug); | ||
71 | GNUNET_GC_free (cfg); | ||
72 | GNUNET_GE_free_context (ectx); | ||
73 | return 1; | ||
74 | } | ||
75 | upnp = init (&capi); | ||
76 | if (upnp == NULL) | ||
77 | { | ||
78 | GNUNET_plugin_unload (plug); | ||
79 | GNUNET_GC_free (cfg); | ||
80 | GNUNET_GE_free_context (ectx); | ||
81 | return 1; | ||
82 | } | ||
83 | for (i = 0; i < 10; i++) | ||
84 | { | ||
85 | if (GNUNET_shutdown_test () != GNUNET_NO) | ||
86 | break; | ||
87 | if (GNUNET_OK == upnp->get_ip (2086, "TCP", &addr)) | ||
88 | { | ||
89 | printf ("UPnP returned external IP %s\n", | ||
90 | inet_ntop (AF_INET, &addr, ntop_buf, INET_ADDRSTRLEN)); | ||
91 | } | ||
92 | else | ||
93 | { | ||
94 | /* we cannot be sure that there is a UPnP-capable | ||
95 | NAT-box out there, so test should not fail | ||
96 | just because of this! */ | ||
97 | printf ("No UPnP response (yet).\n"); | ||
98 | } | ||
99 | GNUNET_thread_sleep (2 * GNUNET_CRON_SECONDS); | ||
100 | } | ||
101 | done = GNUNET_plugin_resolve_function (plug, "release_", GNUNET_YES); | ||
102 | if (done != NULL) | ||
103 | done (); | ||
104 | GNUNET_plugin_unload (plug); | ||
105 | GNUNET_GC_free (cfg); | ||
106 | GNUNET_GE_free_context (ectx); | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | /* end of upnptest.c */ | ||
diff --git a/src/upnp/upnp.c b/src/upnp/upnp.c new file mode 100644 index 000000000..6dc4338a0 --- /dev/null +++ b/src/upnp/upnp.c | |||
@@ -0,0 +1,721 @@ | |||
1 | /** | ||
2 | * @file upnp.c UPnP Implementation | ||
3 | * @ingroup core | ||
4 | * | ||
5 | * gaim | ||
6 | * | ||
7 | * Gaim is the legal property of its developers, whose names are too numerous | ||
8 | * to list here. Please refer to the COPYRIGHT file distributed with this | ||
9 | * source distribution. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
24 | */ | ||
25 | |||
26 | #include "platform.h" | ||
27 | #include "upnp_xmlnode.h" | ||
28 | #include "upnp_util.h" | ||
29 | #include "upnp.h" | ||
30 | |||
31 | #include <curl/curl.h> | ||
32 | |||
33 | #define TRUE GNUNET_YES | ||
34 | #define FALSE GNUNET_NO | ||
35 | #define g_return_if_fail(a) if(!(a)) return; | ||
36 | #define g_return_val_if_fail(a, val) if(!(a)) return (val); | ||
37 | |||
38 | #define HTTP_OK "200 OK" | ||
39 | #define NUM_UDP_ATTEMPTS 2 | ||
40 | #define HTTPMU_HOST_ADDRESS "239.255.255.250" | ||
41 | #define HTTPMU_HOST_PORT 1900 | ||
42 | #define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s" | ||
43 | #define SEARCH_REQUEST_STRING \ | ||
44 | "M-SEARCH * HTTP/1.1\r\n" \ | ||
45 | "MX: 2\r\n" \ | ||
46 | "HOST: 239.255.255.250:1900\r\n" \ | ||
47 | "MAN: \"ssdp:discover\"\r\n" \ | ||
48 | "ST: urn:schemas-upnp-org:service:%s\r\n" \ | ||
49 | "\r\n" | ||
50 | #define WAN_IP_CONN_SERVICE "WANIPConnection:1" | ||
51 | #define WAN_PPP_CONN_SERVICE "WANPPPConnection:1" | ||
52 | #define HTTP_POST_SOAP_HEADER \ | ||
53 | "SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"" | ||
54 | #define HTTP_POST_SIZE_HEADER "CONTENT-LENGTH: %u" | ||
55 | #define SOAP_ACTION \ | ||
56 | "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \ | ||
57 | "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ | ||
58 | "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \ | ||
59 | "<s:Body>\r\n" \ | ||
60 | "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \ | ||
61 | "%s" \ | ||
62 | "</u:%s>\r\n" \ | ||
63 | "</s:Body>\r\n" \ | ||
64 | "</s:Envelope>" | ||
65 | #define PORT_MAPPING_LEASE_TIME "0" | ||
66 | #define PORT_MAPPING_DESCRIPTION "GNUNET_UPNP_PORT_FORWARD" | ||
67 | #define ADD_PORT_MAPPING_PARAMS \ | ||
68 | "<NewRemoteHost></NewRemoteHost>\r\n" \ | ||
69 | "<NewExternalPort>%i</NewExternalPort>\r\n" \ | ||
70 | "<NewProtocol>%s</NewProtocol>\r\n" \ | ||
71 | "<NewInternalPort>%i</NewInternalPort>\r\n" \ | ||
72 | "<NewInternalClient>%s</NewInternalClient>\r\n" \ | ||
73 | "<NewEnabled>1</NewEnabled>\r\n" \ | ||
74 | "<NewPortMappingDescription>" \ | ||
75 | PORT_MAPPING_DESCRIPTION \ | ||
76 | "</NewPortMappingDescription>\r\n" \ | ||
77 | "<NewLeaseDuration>" \ | ||
78 | PORT_MAPPING_LEASE_TIME \ | ||
79 | "</NewLeaseDuration>\r\n" | ||
80 | #define DELETE_PORT_MAPPING_PARAMS \ | ||
81 | "<NewRemoteHost></NewRemoteHost>\r\n" \ | ||
82 | "<NewExternalPort>%i</NewExternalPort>\r\n" \ | ||
83 | "<NewProtocol>%s</NewProtocol>\r\n" | ||
84 | |||
85 | typedef enum | ||
86 | { | ||
87 | GAIM_UPNP_STATUS_UNDISCOVERED = -1, | ||
88 | GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER, | ||
89 | GAIM_UPNP_STATUS_DISCOVERING, | ||
90 | GAIM_UPNP_STATUS_DISCOVERED | ||
91 | } GaimUPnPStatus; | ||
92 | |||
93 | typedef struct | ||
94 | { | ||
95 | GaimUPnPStatus status; | ||
96 | char *control_url; | ||
97 | const char *service_type; | ||
98 | char publicip[16]; | ||
99 | } GaimUPnPControlInfo; | ||
100 | |||
101 | typedef struct | ||
102 | { | ||
103 | const char *service_type; | ||
104 | char *full_url; | ||
105 | char *buf; | ||
106 | unsigned int buf_len; | ||
107 | int sock; | ||
108 | } UPnPDiscoveryData; | ||
109 | |||
110 | static GaimUPnPControlInfo control_info = { | ||
111 | GAIM_UPNP_STATUS_UNDISCOVERED, | ||
112 | NULL, | ||
113 | NULL, | ||
114 | "", | ||
115 | }; | ||
116 | |||
117 | /** | ||
118 | * This is the signature used for functions that act as a callback | ||
119 | * to CURL. | ||
120 | */ | ||
121 | typedef size_t (*GaimUtilFetchUrlCallback) (void *url_data, | ||
122 | size_t size, | ||
123 | size_t nmemb, void *user_data); | ||
124 | |||
125 | |||
126 | |||
127 | static char * | ||
128 | g_strstr_len (const char *haystack, int haystack_len, const char *needle) | ||
129 | { | ||
130 | int i; | ||
131 | |||
132 | g_return_val_if_fail (haystack != NULL, NULL); | ||
133 | g_return_val_if_fail (needle != NULL, NULL); | ||
134 | |||
135 | if (haystack_len < 0) | ||
136 | return strstr (haystack, needle); | ||
137 | else | ||
138 | { | ||
139 | const char *p = haystack; | ||
140 | int needle_len = strlen (needle); | ||
141 | const char *end = haystack + haystack_len - needle_len; | ||
142 | |||
143 | if (needle_len == 0) | ||
144 | return (char *) haystack; | ||
145 | |||
146 | while (*p && p <= end) | ||
147 | { | ||
148 | for (i = 0; i < needle_len; i++) | ||
149 | if (p[i] != needle[i]) | ||
150 | goto next; | ||
151 | |||
152 | return (char *) p; | ||
153 | |||
154 | next: | ||
155 | p++; | ||
156 | } | ||
157 | } | ||
158 | |||
159 | return NULL; | ||
160 | } | ||
161 | |||
162 | static int | ||
163 | gaim_upnp_compare_device (const xmlnode * device, const char *deviceType) | ||
164 | { | ||
165 | xmlnode *deviceTypeNode = xmlnode_get_child (device, "deviceType"); | ||
166 | char *tmp; | ||
167 | int ret; | ||
168 | |||
169 | if (deviceTypeNode == NULL) | ||
170 | return FALSE; | ||
171 | tmp = xmlnode_get_data (deviceTypeNode); | ||
172 | ret = !strcasecmp (tmp, deviceType); | ||
173 | GNUNET_free (tmp); | ||
174 | return ret; | ||
175 | } | ||
176 | |||
177 | static int | ||
178 | gaim_upnp_compare_service (const xmlnode * service, const char *serviceType) | ||
179 | { | ||
180 | xmlnode *serviceTypeNode; | ||
181 | char *tmp; | ||
182 | int ret; | ||
183 | |||
184 | if (service == NULL) | ||
185 | return FALSE; | ||
186 | serviceTypeNode = xmlnode_get_child (service, "serviceType"); | ||
187 | if (serviceTypeNode == NULL) | ||
188 | return FALSE; | ||
189 | tmp = xmlnode_get_data (serviceTypeNode); | ||
190 | ret = !strcasecmp (tmp, serviceType); | ||
191 | GNUNET_free (tmp); | ||
192 | return ret; | ||
193 | } | ||
194 | |||
195 | static char * | ||
196 | gaim_upnp_parse_description_response (const char *httpResponse, | ||
197 | size_t len, | ||
198 | const char *httpURL, | ||
199 | const char *serviceType) | ||
200 | { | ||
201 | char *xmlRoot, *baseURL, *controlURL, *service; | ||
202 | xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode; | ||
203 | char *tmp; | ||
204 | |||
205 | /* find the root of the xml document */ | ||
206 | xmlRoot = g_strstr_len (httpResponse, len, "<root"); | ||
207 | if (xmlRoot == NULL) | ||
208 | return NULL; | ||
209 | if (g_strstr_len (httpResponse, len, "</root") == NULL) | ||
210 | return NULL; | ||
211 | |||
212 | /* create the xml root node */ | ||
213 | xmlRootNode = xmlnode_from_str (xmlRoot, len - (xmlRoot - httpResponse)); | ||
214 | if (xmlRootNode == NULL) | ||
215 | return NULL; | ||
216 | |||
217 | /* get the baseURL of the device */ | ||
218 | baseURLNode = xmlnode_get_child (xmlRootNode, "URLBase"); | ||
219 | if (baseURLNode != NULL) | ||
220 | { | ||
221 | baseURL = xmlnode_get_data (baseURLNode); | ||
222 | } | ||
223 | else | ||
224 | { | ||
225 | baseURL = GNUNET_strdup (httpURL); | ||
226 | } | ||
227 | |||
228 | /* get the serviceType child that has the service type as its data */ | ||
229 | /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its devicelist */ | ||
230 | serviceTypeNode = xmlnode_get_child (xmlRootNode, "device"); | ||
231 | while (!gaim_upnp_compare_device (serviceTypeNode, | ||
232 | "urn:schemas-upnp-org:device:InternetGatewayDevice:1") | ||
233 | && serviceTypeNode != NULL) | ||
234 | { | ||
235 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
236 | } | ||
237 | if (serviceTypeNode == NULL) | ||
238 | { | ||
239 | GNUNET_free (baseURL); | ||
240 | xmlnode_free (xmlRootNode); | ||
241 | return NULL; | ||
242 | } | ||
243 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "deviceList"); | ||
244 | if (serviceTypeNode == NULL) | ||
245 | { | ||
246 | GNUNET_free (baseURL); | ||
247 | xmlnode_free (xmlRootNode); | ||
248 | return NULL; | ||
249 | } | ||
250 | |||
251 | /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */ | ||
252 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "device"); | ||
253 | while (!gaim_upnp_compare_device (serviceTypeNode, | ||
254 | "urn:schemas-upnp-org:device:WANDevice:1") | ||
255 | && serviceTypeNode != NULL) | ||
256 | { | ||
257 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
258 | } | ||
259 | if (serviceTypeNode == NULL) | ||
260 | { | ||
261 | GNUNET_free (baseURL); | ||
262 | xmlnode_free (xmlRootNode); | ||
263 | return NULL; | ||
264 | } | ||
265 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "deviceList"); | ||
266 | if (serviceTypeNode == NULL) | ||
267 | { | ||
268 | GNUNET_free (baseURL); | ||
269 | xmlnode_free (xmlRootNode); | ||
270 | return NULL; | ||
271 | } | ||
272 | |||
273 | /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */ | ||
274 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "device"); | ||
275 | while (serviceTypeNode && !gaim_upnp_compare_device (serviceTypeNode, | ||
276 | "urn:schemas-upnp-org:device:WANConnectionDevice:1")) | ||
277 | { | ||
278 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
279 | } | ||
280 | if (serviceTypeNode == NULL) | ||
281 | { | ||
282 | GNUNET_free (baseURL); | ||
283 | xmlnode_free (xmlRootNode); | ||
284 | return NULL; | ||
285 | } | ||
286 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "serviceList"); | ||
287 | if (serviceTypeNode == NULL) | ||
288 | { | ||
289 | GNUNET_free (baseURL); | ||
290 | xmlnode_free (xmlRootNode); | ||
291 | return NULL; | ||
292 | } | ||
293 | |||
294 | /* get the serviceType variable passed to this function */ | ||
295 | service = g_strdup_printf (SEARCH_REQUEST_DEVICE, serviceType); | ||
296 | serviceTypeNode = xmlnode_get_child (serviceTypeNode, "service"); | ||
297 | while (!gaim_upnp_compare_service (serviceTypeNode, service) && | ||
298 | serviceTypeNode != NULL) | ||
299 | { | ||
300 | serviceTypeNode = xmlnode_get_next_twin (serviceTypeNode); | ||
301 | } | ||
302 | |||
303 | GNUNET_free (service); | ||
304 | if (serviceTypeNode == NULL) | ||
305 | { | ||
306 | GNUNET_free (baseURL); | ||
307 | xmlnode_free (xmlRootNode); | ||
308 | return NULL; | ||
309 | } | ||
310 | |||
311 | /* get the controlURL of the service */ | ||
312 | if ((controlURLNode = xmlnode_get_child (serviceTypeNode, | ||
313 | "controlURL")) == NULL) | ||
314 | { | ||
315 | GNUNET_free (baseURL); | ||
316 | xmlnode_free (xmlRootNode); | ||
317 | return NULL; | ||
318 | } | ||
319 | |||
320 | tmp = xmlnode_get_data (controlURLNode); | ||
321 | if (baseURL && !gaim_str_has_prefix (tmp, "http://") && | ||
322 | !gaim_str_has_prefix (tmp, "HTTP://")) | ||
323 | { | ||
324 | if (tmp[0] == '/') | ||
325 | { | ||
326 | size_t len; | ||
327 | const char *end; | ||
328 | /* absolute path */ | ||
329 | end = strstr (&baseURL[strlen ("http://")], "/"); | ||
330 | if (end == NULL) | ||
331 | len = strlen (&baseURL[strlen ("http://")]); | ||
332 | else | ||
333 | len = end - &baseURL[strlen ("http://")]; | ||
334 | controlURL = g_strdup_printf ("http://%.*s%s", | ||
335 | len, | ||
336 | &baseURL[strlen ("http://")], tmp); | ||
337 | } | ||
338 | else | ||
339 | { | ||
340 | controlURL = g_strdup_printf ("%s%s", baseURL, tmp); | ||
341 | } | ||
342 | GNUNET_free (tmp); | ||
343 | } | ||
344 | else | ||
345 | { | ||
346 | controlURL = tmp; | ||
347 | } | ||
348 | GNUNET_free (baseURL); | ||
349 | xmlnode_free (xmlRootNode); | ||
350 | |||
351 | return controlURL; | ||
352 | } | ||
353 | |||
354 | #define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_log(GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0); | ||
355 | |||
356 | /** | ||
357 | * Do the generic curl setup. | ||
358 | */ | ||
359 | static int | ||
360 | setup_curl (const char *proxy, CURL * curl) | ||
361 | { | ||
362 | int ret; | ||
363 | |||
364 | CURL_EASY_SETOPT (curl, CURLOPT_FAILONERROR, 1); | ||
365 | if (strlen (proxy) > 0) | ||
366 | CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy); | ||
367 | CURL_EASY_SETOPT (curl, CURLOPT_BUFFERSIZE, 1024); /* a bit more than one HELLO */ | ||
368 | CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 150L); | ||
369 | /* NOTE: use of CONNECTTIMEOUT without also | ||
370 | setting NOSIGNAL results in really weird | ||
371 | crashes on my system! */ | ||
372 | CURL_EASY_SETOPT (curl, CURLOPT_NOSIGNAL, 1); | ||
373 | return GNUNET_OK; | ||
374 | } | ||
375 | |||
376 | static int | ||
377 | gaim_upnp_generate_action_message_and_send (const char *proxy, | ||
378 | const char *actionName, | ||
379 | const char *actionParams, | ||
380 | GaimUtilFetchUrlCallback cb, | ||
381 | void *cb_data) | ||
382 | { | ||
383 | CURL *curl; | ||
384 | int ret; | ||
385 | char *soapHeader; | ||
386 | char *sizeHeader; | ||
387 | char *soapMessage; | ||
388 | struct curl_slist *headers = NULL; | ||
389 | |||
390 | GNUNET_assert (cb != NULL); | ||
391 | if (0 != curl_global_init (CURL_GLOBAL_WIN32)) | ||
392 | return GNUNET_SYSERR; | ||
393 | /* set the soap message */ | ||
394 | soapMessage = g_strdup_printf (SOAP_ACTION, | ||
395 | actionName, | ||
396 | control_info.service_type, | ||
397 | actionParams, actionName); | ||
398 | soapHeader = g_strdup_printf (HTTP_POST_SOAP_HEADER, | ||
399 | control_info.service_type, actionName); | ||
400 | sizeHeader = g_strdup_printf (HTTP_POST_SIZE_HEADER, strlen (soapMessage)); | ||
401 | curl = curl_easy_init (); | ||
402 | setup_curl (proxy, curl); | ||
403 | CURL_EASY_SETOPT (curl, CURLOPT_URL, control_info.control_url); | ||
404 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, cb); | ||
405 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cb_data); | ||
406 | CURL_EASY_SETOPT (curl, CURLOPT_POST, 1); | ||
407 | headers = curl_slist_append (headers, | ||
408 | "CONTENT-TYPE: text/xml ; charset=\"utf-8\""); | ||
409 | headers = curl_slist_append (headers, soapHeader); | ||
410 | headers = curl_slist_append (headers, sizeHeader); | ||
411 | CURL_EASY_SETOPT (curl, CURLOPT_HTTPHEADER, headers); | ||
412 | CURL_EASY_SETOPT (curl, CURLOPT_POSTFIELDS, soapMessage); | ||
413 | CURL_EASY_SETOPT (curl, CURLOPT_POSTFIELDSIZE, strlen (soapMessage)); | ||
414 | CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 1L); | ||
415 | CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 1L); | ||
416 | CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 2L); | ||
417 | /* NOTE: use of CONNECTTIMEOUT without also | ||
418 | setting NOSIGNAL results in really weird | ||
419 | crashes on my system! */ | ||
420 | CURL_EASY_SETOPT (curl, CURLOPT_NOSIGNAL, 1); | ||
421 | if (ret == CURLE_OK) | ||
422 | ret = curl_easy_perform (curl); | ||
423 | #if 0 | ||
424 | if (ret != CURLE_OK) | ||
425 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
426 | _ | ||
427 | ("%s failed for url `%s' and post-data `%s' at %s:%d: `%s'\n"), | ||
428 | "curl_easy_perform", control_info.control_url, soapMessage, | ||
429 | __FILE__, __LINE__, curl_easy_strerror (ret)); | ||
430 | #endif | ||
431 | curl_slist_free_all (headers); | ||
432 | curl_easy_cleanup (curl); | ||
433 | curl_global_cleanup (); | ||
434 | GNUNET_free (sizeHeader); | ||
435 | GNUNET_free (soapMessage); | ||
436 | GNUNET_free (soapHeader); | ||
437 | if (ret != CURLE_OK) | ||
438 | return GNUNET_SYSERR; | ||
439 | return GNUNET_OK; | ||
440 | } | ||
441 | |||
442 | |||
443 | static size_t | ||
444 | looked_up_public_ip_cb (void *url_data, | ||
445 | size_t size, size_t nmemb, void *user_data) | ||
446 | { | ||
447 | UPnPDiscoveryData *dd = user_data; | ||
448 | size_t len = size * nmemb; | ||
449 | const char *temp; | ||
450 | const char *temp2; | ||
451 | |||
452 | if (len + dd->buf_len > 1024 * 1024 * 4) | ||
453 | return 0; /* refuse to process - too big! */ | ||
454 | GNUNET_array_grow (dd->buf, dd->buf_len, dd->buf_len + len); | ||
455 | memcpy (&dd->buf[dd->buf_len - len], url_data, len); | ||
456 | if (dd->buf_len == 0) | ||
457 | return len; | ||
458 | /* extract the ip, or see if there is an error */ | ||
459 | if ((temp = g_strstr_len (dd->buf, | ||
460 | dd->buf_len, "<NewExternalIPAddress")) == NULL) | ||
461 | return len; | ||
462 | if (!(temp = g_strstr_len (temp, dd->buf_len - (temp - dd->buf), ">"))) | ||
463 | return len; | ||
464 | if (!(temp2 = g_strstr_len (temp, dd->buf_len - (temp - dd->buf), "<"))) | ||
465 | return len; | ||
466 | memset (control_info.publicip, 0, sizeof (control_info.publicip)); | ||
467 | if (temp2 - temp >= sizeof (control_info.publicip)) | ||
468 | temp2 = temp + sizeof (control_info.publicip) - 1; | ||
469 | memcpy (control_info.publicip, temp + 1, temp2 - (temp + 1)); | ||
470 | GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK, | ||
471 | _("upnp: NAT Returned IP: %s\n"), control_info.publicip); | ||
472 | return len; | ||
473 | } | ||
474 | |||
475 | |||
476 | static size_t | ||
477 | ignore_response (void *url_data, size_t size, size_t nmemb, void *user_data) | ||
478 | { | ||
479 | return size * nmemb; | ||
480 | } | ||
481 | |||
482 | /** | ||
483 | * Process downloaded bits of service description. | ||
484 | */ | ||
485 | static size_t | ||
486 | upnp_parse_description_cb (void *httpResponse, | ||
487 | size_t size, size_t nmemb, void *user_data) | ||
488 | { | ||
489 | UPnPDiscoveryData *dd = user_data; | ||
490 | size_t len = size * nmemb; | ||
491 | char *control_url = NULL; | ||
492 | |||
493 | if (len + dd->buf_len > 1024 * 1024 * 4) | ||
494 | return len; /* refuse to process - too big! */ | ||
495 | GNUNET_array_grow (dd->buf, dd->buf_len, dd->buf_len + len); | ||
496 | memcpy (&dd->buf[dd->buf_len - len], httpResponse, len); | ||
497 | if (dd->buf_len > 0) | ||
498 | control_url = gaim_upnp_parse_description_response (dd->buf, | ||
499 | dd->buf_len, | ||
500 | dd->full_url, | ||
501 | dd->service_type); | ||
502 | control_info.status = control_url ? GAIM_UPNP_STATUS_DISCOVERED | ||
503 | : GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER; | ||
504 | GNUNET_free_non_null (control_info.control_url); | ||
505 | control_info.control_url = control_url; | ||
506 | control_info.service_type = dd->service_type; | ||
507 | return len; | ||
508 | } | ||
509 | |||
510 | static int | ||
511 | gaim_upnp_parse_description (char *proxy, UPnPDiscoveryData * dd) | ||
512 | { | ||
513 | CURL *curl; | ||
514 | int ret; | ||
515 | |||
516 | if (0 != curl_global_init (CURL_GLOBAL_WIN32)) | ||
517 | return GNUNET_SYSERR; | ||
518 | curl = curl_easy_init (); | ||
519 | setup_curl (proxy, curl); | ||
520 | ret = CURLE_OK; | ||
521 | CURL_EASY_SETOPT (curl, CURLOPT_URL, dd->full_url); | ||
522 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &upnp_parse_description_cb); | ||
523 | CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, dd); | ||
524 | CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 1L); | ||
525 | CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 1L); | ||
526 | CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 2L); | ||
527 | |||
528 | /* NOTE: use of CONNECTTIMEOUT without also | ||
529 | setting NOSIGNAL results in really weird | ||
530 | crashes on my system! */ | ||
531 | CURL_EASY_SETOPT (curl, CURLOPT_NOSIGNAL, 1); | ||
532 | ret = curl_easy_perform (curl); | ||
533 | if (ret != CURLE_OK) | ||
534 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
535 | _("%s failed at %s:%d: `%s'\n"), | ||
536 | "curl_easy_perform", __FILE__, __LINE__, | ||
537 | curl_easy_strerror (ret)); | ||
538 | curl_easy_cleanup (curl); | ||
539 | curl_global_cleanup (); | ||
540 | if (control_info.control_url == NULL) | ||
541 | return GNUNET_SYSERR; | ||
542 | return GNUNET_OK; | ||
543 | } | ||
544 | |||
545 | int | ||
546 | gaim_upnp_discover (struct GNUNET_CONFIGURATION_Handle *cfg, int sock) | ||
547 | { | ||
548 | char *proxy; | ||
549 | socklen_t avail; | ||
550 | struct sockaddr_in server; | ||
551 | int retry_count; | ||
552 | char *sendMessage; | ||
553 | size_t totalSize; | ||
554 | int sentSuccess; | ||
555 | char buf[65536]; | ||
556 | int buf_len; | ||
557 | const char *startDescURL; | ||
558 | const char *endDescURL; | ||
559 | int ret; | ||
560 | UPnPDiscoveryData dd; | ||
561 | struct sockaddr *sa; | ||
562 | |||
563 | memset (&dd, 0, sizeof (UPnPDiscoveryData)); | ||
564 | if (control_info.status == GAIM_UPNP_STATUS_DISCOVERING) | ||
565 | return GNUNET_NO; | ||
566 | dd.sock = sock; | ||
567 | memset (&server, 0, sizeof (struct sockaddr_in)); | ||
568 | server.sin_family = AF_INET; | ||
569 | avail = sizeof (struct sockaddr_in); | ||
570 | sa = (struct sockaddr *) &server; | ||
571 | if (GNUNET_OK != | ||
572 | GNUNET_get_ip_from_hostname (HTTPMU_HOST_ADDRESS, AF_INET, &sa, &avail)) | ||
573 | { | ||
574 | return GNUNET_SYSERR; | ||
575 | } | ||
576 | server.sin_port = htons (HTTPMU_HOST_PORT); | ||
577 | control_info.status = GAIM_UPNP_STATUS_DISCOVERING; | ||
578 | |||
579 | /* because we are sending over UDP, if there is a failure | ||
580 | we should retry the send NUM_UDP_ATTEMPTS times. Also, | ||
581 | try different requests for WANIPConnection and WANPPPConnection */ | ||
582 | for (retry_count = 0; retry_count < NUM_UDP_ATTEMPTS; retry_count++) | ||
583 | { | ||
584 | sentSuccess = FALSE; | ||
585 | if ((retry_count % 2) == 0) | ||
586 | dd.service_type = WAN_IP_CONN_SERVICE; | ||
587 | else | ||
588 | dd.service_type = WAN_PPP_CONN_SERVICE; | ||
589 | sendMessage = g_strdup_printf (SEARCH_REQUEST_STRING, dd.service_type); | ||
590 | totalSize = strlen (sendMessage); | ||
591 | do | ||
592 | { | ||
593 | if (SENDTO (dd.sock, | ||
594 | sendMessage, | ||
595 | totalSize, | ||
596 | 0, | ||
597 | (struct sockaddr *) &server, | ||
598 | sizeof (struct sockaddr_in)) == totalSize) | ||
599 | { | ||
600 | sentSuccess = TRUE; | ||
601 | break; | ||
602 | } | ||
603 | } | ||
604 | while (((errno == EINTR) || (errno == EAGAIN)) && | ||
605 | (GNUNET_shutdown_test () == GNUNET_NO)); | ||
606 | GNUNET_free (sendMessage); | ||
607 | if (sentSuccess) | ||
608 | break; | ||
609 | } | ||
610 | if (sentSuccess == FALSE) | ||
611 | return GNUNET_SYSERR; | ||
612 | |||
613 | /* try to read response */ | ||
614 | do | ||
615 | { | ||
616 | buf_len = recv (dd.sock, buf, sizeof (buf) - 1, 0); | ||
617 | if (buf_len > 0) | ||
618 | { | ||
619 | buf[buf_len] = '\0'; | ||
620 | break; | ||
621 | } | ||
622 | else if (errno != EINTR) | ||
623 | { | ||
624 | continue; | ||
625 | } | ||
626 | } | ||
627 | while ((errno == EINTR) && (GNUNET_shutdown_test () == GNUNET_NO)); | ||
628 | |||
629 | /* parse the response, and see if it was a success */ | ||
630 | if (g_strstr_len (buf, buf_len, HTTP_OK) == NULL) | ||
631 | return GNUNET_SYSERR; | ||
632 | if ((startDescURL = g_strstr_len (buf, buf_len, "http://")) == NULL) | ||
633 | return GNUNET_SYSERR; | ||
634 | |||
635 | endDescURL = g_strstr_len (startDescURL, | ||
636 | buf_len - (startDescURL - buf), "\r"); | ||
637 | if (endDescURL == NULL) | ||
638 | endDescURL = g_strstr_len (startDescURL, | ||
639 | buf_len - (startDescURL - buf), "\n"); | ||
640 | if (endDescURL == NULL) | ||
641 | return GNUNET_SYSERR; | ||
642 | if (endDescURL == startDescURL) | ||
643 | return GNUNET_SYSERR; | ||
644 | dd.full_url = GNUNET_strdup (startDescURL); | ||
645 | dd.full_url[endDescURL - startDescURL] = '\0'; | ||
646 | proxy = NULL; | ||
647 | GNUNET_CONFIGURATION_get_value_string (cfg, | ||
648 | "GNUNETD", "HTTP-PROXY", &proxy); | ||
649 | ret = gaim_upnp_parse_description (proxy, &dd); | ||
650 | GNUNET_free (dd.full_url); | ||
651 | GNUNET_array_grow (dd.buf, dd.buf_len, 0); | ||
652 | if (ret == GNUNET_OK) | ||
653 | { | ||
654 | ret = gaim_upnp_generate_action_message_and_send (proxy, | ||
655 | "GetExternalIPAddress", | ||
656 | "", | ||
657 | looked_up_public_ip_cb, | ||
658 | &dd); | ||
659 | GNUNET_array_grow (dd.buf, dd.buf_len, 0); | ||
660 | } | ||
661 | GNUNET_free (proxy); | ||
662 | return ret; | ||
663 | } | ||
664 | |||
665 | const char * | ||
666 | gaim_upnp_get_public_ip () | ||
667 | { | ||
668 | if ((control_info.status == GAIM_UPNP_STATUS_DISCOVERED) | ||
669 | && (strlen (control_info.publicip) > 0)) | ||
670 | return control_info.publicip; | ||
671 | return NULL; | ||
672 | } | ||
673 | |||
674 | int | ||
675 | gaim_upnp_change_port_mapping (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
676 | int do_add, | ||
677 | unsigned short portmap, const char *protocol) | ||
678 | { | ||
679 | const char *action_name; | ||
680 | char *action_params; | ||
681 | char *internal_ip; | ||
682 | char *proxy; | ||
683 | int ret; | ||
684 | |||
685 | if (control_info.status != GAIM_UPNP_STATUS_DISCOVERED) | ||
686 | return GNUNET_NO; | ||
687 | if (do_add) | ||
688 | { | ||
689 | internal_ip = GNUNET_upnp_get_internal_ip (cfg); | ||
690 | if (internal_ip == NULL) | ||
691 | { | ||
692 | gaim_debug_error ("upnp", | ||
693 | "gaim_upnp_set_port_mapping(): couldn't get local ip\n"); | ||
694 | return GNUNET_NO; | ||
695 | } | ||
696 | action_name = "AddPortMapping"; | ||
697 | action_params = g_strdup_printf (ADD_PORT_MAPPING_PARAMS, | ||
698 | portmap, | ||
699 | protocol, portmap, internal_ip); | ||
700 | GNUNET_free (internal_ip); | ||
701 | } | ||
702 | else | ||
703 | { | ||
704 | action_name = "DeletePortMapping"; | ||
705 | action_params = g_strdup_printf (DELETE_PORT_MAPPING_PARAMS, | ||
706 | portmap, protocol); | ||
707 | } | ||
708 | proxy = NULL; | ||
709 | GNUNET_CONFIGURATION_get_value_string (cfg, | ||
710 | "GNUNETD", "HTTP-PROXY", &proxy); | ||
711 | ret = | ||
712 | gaim_upnp_generate_action_message_and_send (proxy, action_name, | ||
713 | action_params, | ||
714 | &ignore_response, NULL); | ||
715 | |||
716 | GNUNET_free (action_params); | ||
717 | GNUNET_free (proxy); | ||
718 | return ret; | ||
719 | } | ||
720 | |||
721 | /* end of upnp.c */ | ||
diff --git a/src/upnp/upnp.h b/src/upnp/upnp.h new file mode 100644 index 000000000..1f99c5352 --- /dev/null +++ b/src/upnp/upnp.h | |||
@@ -0,0 +1,82 @@ | |||
1 | /** | ||
2 | * @file upnp.h Universal Plug N Play API | ||
3 | * @ingroup core | ||
4 | * | ||
5 | * gaim | ||
6 | * | ||
7 | * Gaim is the legal property of its developers, whose names are too numerous | ||
8 | * to list here. Please refer to the COPYRIGHT file distributed with this | ||
9 | * source distribution. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
24 | */ | ||
25 | |||
26 | #ifndef _GAIM_UPNP_H_ | ||
27 | #define _GAIM_UPNP_H_ | ||
28 | |||
29 | #include <libxml/parser.h> | ||
30 | #include <string.h> | ||
31 | #include "gnunet_util_lib.h" | ||
32 | |||
33 | #ifdef __cplusplus | ||
34 | extern "C" | ||
35 | { | ||
36 | #if 0 /* keep Emacsens' auto-indent happy */ | ||
37 | } | ||
38 | #endif | ||
39 | #endif | ||
40 | |||
41 | /** | ||
42 | * Sends a discovery request to search for a UPnP enabled IGD that | ||
43 | * contains the WANIPConnection service that will allow us to receive the | ||
44 | * public IP address of the IGD, and control it for forwarding ports. | ||
45 | * The result will be cached for further use. | ||
46 | */ | ||
47 | int gaim_upnp_discover (struct GNUNET_CONFIGURATION_Handle *cfg, int sock); | ||
48 | |||
49 | /** | ||
50 | * Gets the IP address from a UPnP enabled IGD that sits on the local | ||
51 | * network, so when getting the network IP, instead of returning the | ||
52 | * local network IP, the public IP is retrieved. This is a cached value from | ||
53 | * the time of the UPnP discovery. | ||
54 | * | ||
55 | * @return The IP address of the network, or NULL if something went wrong | ||
56 | */ | ||
57 | const char *gaim_upnp_get_public_ip (void); | ||
58 | |||
59 | /** | ||
60 | * Maps Ports in a UPnP enabled IGD that sits on the local network to | ||
61 | * this gaim client. Essentially, this function takes care of the port | ||
62 | * forwarding so things like file transfers can work behind NAT firewalls | ||
63 | * | ||
64 | * @param portmap The port to map to this client | ||
65 | * @param protocol The protocol to map, either "TCP" or "UDP" | ||
66 | * @param do_add TRUE/GNUNET_YES to add, FALSE/GNUNET_NO to remove | ||
67 | * @param cb an optional callback function to be notified when the mapping | ||
68 | * addition is complete | ||
69 | * @param cb_data Extra data to be passed to the callback | ||
70 | */ | ||
71 | int gaim_upnp_change_port_mapping (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
72 | int do_add, | ||
73 | uint16_t portmap, const char *protocol); | ||
74 | |||
75 | #if 0 /* keep Emacsens' auto-indent happy */ | ||
76 | { | ||
77 | #endif | ||
78 | #ifdef __cplusplus | ||
79 | } | ||
80 | #endif | ||
81 | |||
82 | #endif /* _GAIM_UPNP_H_ */ | ||
diff --git a/src/upnp/upnp_init.c b/src/upnp/upnp_init.c new file mode 100644 index 000000000..3dc0d2b7c --- /dev/null +++ b/src/upnp/upnp_init.c | |||
@@ -0,0 +1,208 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | (C) 2006 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file src/transports/upnp/init.c | ||
23 | * @brief API for UPnP access | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | |||
27 | #include "platform.h" | ||
28 | #include "gnunet_util.h" | ||
29 | #include "upnp.h" | ||
30 | #include "gnunet_upnp_service.h" | ||
31 | #include "gnunet_core.h" | ||
32 | |||
33 | static struct GNUNET_GE_Context *ectx; | ||
34 | |||
35 | static struct GNUNET_GC_Configuration *cfg; | ||
36 | |||
37 | static struct GNUNET_CronManager *cron; | ||
38 | |||
39 | static struct GNUNET_Mutex *lock; | ||
40 | |||
41 | typedef struct | ||
42 | { | ||
43 | unsigned short port; | ||
44 | const char *proto; | ||
45 | } PMap; | ||
46 | |||
47 | static PMap *maps; | ||
48 | |||
49 | static unsigned int maps_size; | ||
50 | |||
51 | static struct GNUNET_ThreadHandle *discovery; | ||
52 | |||
53 | static int discovery_socket; | ||
54 | |||
55 | /** | ||
56 | * Obtain the public/external IP address. | ||
57 | * | ||
58 | * @return GNUNET_SYSERR on error, GNUNET_OK on success | ||
59 | */ | ||
60 | static int | ||
61 | gnunet_upnp_get_public_ip (struct in_addr *address) | ||
62 | { | ||
63 | const char *ip; | ||
64 | socklen_t socklen; | ||
65 | struct sockaddr *sa; | ||
66 | struct sockaddr_in s4; | ||
67 | int ret; | ||
68 | |||
69 | ip = gaim_upnp_get_public_ip (); | ||
70 | if (ip == NULL) | ||
71 | return GNUNET_SYSERR; | ||
72 | socklen = sizeof (struct sockaddr_in); | ||
73 | sa = (struct sockaddr *) &s4; | ||
74 | ret = GNUNET_get_ip_from_hostname (NULL, ip, AF_INET, &sa, &socklen); | ||
75 | if (ret == GNUNET_OK) | ||
76 | *address = s4.sin_addr; | ||
77 | return ret; | ||
78 | } | ||
79 | |||
80 | static void | ||
81 | kill_discovery () | ||
82 | { | ||
83 | void *unused; | ||
84 | |||
85 | if (discovery != NULL) | ||
86 | { | ||
87 | SHUTDOWN (discovery_socket, SHUT_RDWR); | ||
88 | CLOSE (discovery_socket); | ||
89 | GNUNET_thread_join (discovery, &unused); | ||
90 | discovery = NULL; | ||
91 | } | ||
92 | } | ||
93 | |||
94 | static void * | ||
95 | discover_thread () | ||
96 | { | ||
97 | gaim_upnp_discover (ectx, cfg, discovery_socket); | ||
98 | return NULL; | ||
99 | } | ||
100 | |||
101 | /** | ||
102 | * Periodically try to (re)discover UPnP access points. | ||
103 | */ | ||
104 | static void | ||
105 | discover (void *unused) | ||
106 | { | ||
107 | kill_discovery (); | ||
108 | discovery_socket = SOCKET (PF_INET, SOCK_DGRAM, 0); | ||
109 | if (discovery_socket == -1) | ||
110 | return; | ||
111 | discovery = GNUNET_thread_create (&discover_thread, NULL, 1024 * 128); | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * Periodically repeat our requests for port mappings. | ||
116 | */ | ||
117 | static void | ||
118 | portmap (void *unused) | ||
119 | { | ||
120 | unsigned int i; | ||
121 | |||
122 | GNUNET_mutex_lock (lock); | ||
123 | for (i = 0; i < maps_size; i++) | ||
124 | gaim_upnp_change_port_mapping (ectx, | ||
125 | cfg, GNUNET_NO, maps[i].port, | ||
126 | maps[i].proto); | ||
127 | GNUNET_mutex_unlock (lock); | ||
128 | } | ||
129 | |||
130 | |||
131 | /** | ||
132 | * Get the external IP address for the local machine. | ||
133 | * | ||
134 | * @return GNUNET_SYSERR on error, GNUNET_OK on success | ||
135 | */ | ||
136 | static int | ||
137 | gnunet_upnp_get_ip (unsigned short port, | ||
138 | const char *protocol, struct in_addr *address) | ||
139 | { | ||
140 | unsigned int i; | ||
141 | |||
142 | GNUNET_mutex_lock (lock); | ||
143 | for (i = 0; i < maps_size; i++) | ||
144 | if ((0 == strcmp (maps[i].proto, protocol)) && (maps[i].port == port)) | ||
145 | break; | ||
146 | if (i == maps_size) | ||
147 | { | ||
148 | /* new entry! */ | ||
149 | GNUNET_array_grow (maps, maps_size, maps_size + 1); | ||
150 | maps[i].proto = protocol; | ||
151 | maps[i].port = port; | ||
152 | gaim_upnp_change_port_mapping (ectx, cfg, GNUNET_YES, port, protocol); | ||
153 | } | ||
154 | GNUNET_mutex_unlock (lock); | ||
155 | return gnunet_upnp_get_public_ip (address); | ||
156 | } | ||
157 | |||
158 | |||
159 | /** | ||
160 | * Get the external IP address for the local machine. | ||
161 | */ | ||
162 | GNUNET_UPnP_ServiceAPI * | ||
163 | provide_module_upnp (GNUNET_CoreAPIForPlugins * capi) | ||
164 | { | ||
165 | static GNUNET_UPnP_ServiceAPI api; | ||
166 | |||
167 | ectx = capi->ectx; | ||
168 | cfg = capi->cfg; | ||
169 | cron = GNUNET_cron_create (ectx); | ||
170 | lock = GNUNET_mutex_create (GNUNET_NO); | ||
171 | GNUNET_cron_start (cron); | ||
172 | GNUNET_cron_add_job (cron, &discover, 0, 5 * GNUNET_CRON_MINUTES, NULL); | ||
173 | GNUNET_cron_add_job (cron, &portmap, 150 * GNUNET_CRON_SECONDS, | ||
174 | 5 * GNUNET_CRON_MINUTES, NULL); | ||
175 | api.get_ip = gnunet_upnp_get_ip; | ||
176 | return &api; | ||
177 | } | ||
178 | |||
179 | /** | ||
180 | * Shutdown UPNP. | ||
181 | */ | ||
182 | int | ||
183 | release_module_upnp () | ||
184 | { | ||
185 | unsigned int i; | ||
186 | |||
187 | if (cron == NULL) | ||
188 | return GNUNET_SYSERR; /* not loaded! */ | ||
189 | for (i = 0; i < maps_size; i++) | ||
190 | gaim_upnp_change_port_mapping (ectx, | ||
191 | cfg, GNUNET_NO, maps[i].port, | ||
192 | maps[i].proto); | ||
193 | GNUNET_cron_stop (cron); | ||
194 | GNUNET_cron_del_job (cron, &discover, 5 * GNUNET_CRON_MINUTES, NULL); | ||
195 | GNUNET_cron_del_job (cron, &portmap, 5 * GNUNET_CRON_MINUTES, NULL); | ||
196 | GNUNET_cron_destroy (cron); | ||
197 | kill_discovery (); | ||
198 | cron = NULL; | ||
199 | GNUNET_mutex_destroy (lock); | ||
200 | lock = NULL; | ||
201 | GNUNET_array_grow (maps, maps_size, 0); | ||
202 | ectx = NULL; | ||
203 | cfg = NULL; | ||
204 | return GNUNET_OK; | ||
205 | } | ||
206 | |||
207 | |||
208 | /* end of init.c */ | ||
diff --git a/src/upnp/upnp_ip.c b/src/upnp/upnp_ip.c new file mode 100644 index 000000000..3716fc671 --- /dev/null +++ b/src/upnp/upnp_ip.c | |||
@@ -0,0 +1,47 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2006, 2007 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file transports/upnp/ip.c | ||
23 | * @brief code to determine the IP of the local machine | ||
24 | * | ||
25 | * @author Christian Grothoff | ||
26 | */ | ||
27 | |||
28 | #include <stdlib.h> | ||
29 | #include "platform.h" | ||
30 | #include "gnunet_util.h" | ||
31 | #include "ip.h" | ||
32 | |||
33 | /** | ||
34 | * Get the IP address for the local machine. | ||
35 | * @return NULL on error | ||
36 | */ | ||
37 | char * | ||
38 | GNUNET_upnp_get_internal_ip (struct GNUNET_GC_Configuration *cfg, | ||
39 | struct GNUNET_GE_Context *ectx) | ||
40 | { | ||
41 | struct in_addr address; | ||
42 | |||
43 | return GNUNET_get_local_ip (cfg, ectx, &address); | ||
44 | } | ||
45 | |||
46 | |||
47 | /* end of ip.c */ | ||
diff --git a/src/upnp/upnp_ip.h b/src/upnp/upnp_ip.h new file mode 100644 index 000000000..adc06bbd5 --- /dev/null +++ b/src/upnp/upnp_ip.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2001, 2002, 2003, 2004, 2005 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file transports/upnp/ip.h | ||
23 | * @brief | ||
24 | * | ||
25 | * @author Christian Grothoff | ||
26 | */ | ||
27 | |||
28 | #ifndef IP_H | ||
29 | #define IP_H | ||
30 | |||
31 | |||
32 | /** | ||
33 | * Get the IP address for the local machine. | ||
34 | * @return NULL on error | ||
35 | */ | ||
36 | char *GNUNET_upnp_get_internal_ip (struct GNUNET_GC_Configuration *cfg, | ||
37 | struct GNUNET_GE_Context *ectx); | ||
38 | |||
39 | |||
40 | #endif | ||
diff --git a/src/upnp/upnp_util.c b/src/upnp/upnp_util.c new file mode 100644 index 000000000..cef40b578 --- /dev/null +++ b/src/upnp/upnp_util.c | |||
@@ -0,0 +1,152 @@ | |||
1 | /* | ||
2 | * @file util.h Utility Functions | ||
3 | * @ingroup core | ||
4 | * | ||
5 | * Gaim is the legal property of its developers, whose names are too numerous | ||
6 | * to list here. Please refer to the COPYRIGHT file distributed with this | ||
7 | * source distribution. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
22 | */ | ||
23 | |||
24 | #include "platform.h" | ||
25 | #include "util.h" | ||
26 | #include "gnunet_util.h" | ||
27 | |||
28 | /* Returns a NULL-terminated string after unescaping an entity | ||
29 | * (eg. &, < & etc.) starting at s. Returns NULL on failure.*/ | ||
30 | static char * | ||
31 | detect_entity (const char *text, int *length) | ||
32 | { | ||
33 | const char *pln; | ||
34 | int len; | ||
35 | int pound; | ||
36 | char b[7]; | ||
37 | char *buf; | ||
38 | |||
39 | if (!text || *text != '&') | ||
40 | return NULL; | ||
41 | |||
42 | #define IS_ENTITY(s) (!strncasecmp(text, s, (len = sizeof(s) - 1))) | ||
43 | |||
44 | if (IS_ENTITY ("&")) | ||
45 | pln = "&"; | ||
46 | else if (IS_ENTITY ("<")) | ||
47 | pln = "<"; | ||
48 | else if (IS_ENTITY (">")) | ||
49 | pln = ">"; | ||
50 | else if (IS_ENTITY (" ")) | ||
51 | pln = " "; | ||
52 | else if (IS_ENTITY ("©")) | ||
53 | pln = "\302\251"; /* or use g_unichar_to_utf8(0xa9); */ | ||
54 | else if (IS_ENTITY (""")) | ||
55 | pln = "\""; | ||
56 | else if (IS_ENTITY ("®")) | ||
57 | pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */ | ||
58 | else if (IS_ENTITY ("'")) | ||
59 | pln = "\'"; | ||
60 | else if (*(text + 1) == '#' && (sscanf (text, "&#%u;", £) == 1) && | ||
61 | pound != 0 && *(text + 3 + (int) log10 (pound)) == ';') | ||
62 | { | ||
63 | buf = GNUNET_convert_string_to_utf8 (NULL, | ||
64 | (const char *) £, | ||
65 | 2, "UNICODE"); | ||
66 | if (strlen (buf) > 6) | ||
67 | buf[6] = '\0'; | ||
68 | strcpy (b, buf); | ||
69 | pln = b; | ||
70 | GNUNET_free (buf); | ||
71 | len = 2; | ||
72 | while (isdigit ((int) text[len])) | ||
73 | len++; | ||
74 | if (text[len] == ';') | ||
75 | len++; | ||
76 | } | ||
77 | else | ||
78 | return NULL; | ||
79 | |||
80 | if (length) | ||
81 | *length = len; | ||
82 | return GNUNET_strdup (pln); | ||
83 | } | ||
84 | |||
85 | char * | ||
86 | g_strdup_printf (const char *fmt, ...) | ||
87 | { | ||
88 | size_t size; | ||
89 | char *buf; | ||
90 | va_list va; | ||
91 | |||
92 | va_start (va, fmt); | ||
93 | size = VSNPRINTF (NULL, 0, fmt, va) + 1; | ||
94 | va_end (va); | ||
95 | buf = GNUNET_malloc (size); | ||
96 | va_start (va, fmt); | ||
97 | VSNPRINTF (buf, size, fmt, va); | ||
98 | va_end (va); | ||
99 | return buf; | ||
100 | } | ||
101 | |||
102 | char * | ||
103 | gaim_unescape_html (const char *html) | ||
104 | { | ||
105 | if (html != NULL) | ||
106 | { | ||
107 | const char *c = html; | ||
108 | char *ret = GNUNET_strdup (""); | ||
109 | char *app; | ||
110 | while (*c) | ||
111 | { | ||
112 | int len; | ||
113 | char *ent; | ||
114 | |||
115 | if ((ent = detect_entity (c, &len)) != NULL) | ||
116 | { | ||
117 | app = g_strdup_printf ("%s%s", ret, ent); | ||
118 | GNUNET_free (ret); | ||
119 | ret = app; | ||
120 | c += len; | ||
121 | GNUNET_free (ent); | ||
122 | } | ||
123 | else if (!strncmp (c, "<br>", 4)) | ||
124 | { | ||
125 | app = g_strdup_printf ("%s%s", ret, "\n"); | ||
126 | GNUNET_free (ret); | ||
127 | ret = app; | ||
128 | c += 4; | ||
129 | } | ||
130 | else | ||
131 | { | ||
132 | app = g_strdup_printf ("%s%c", ret, *c); | ||
133 | GNUNET_free (ret); | ||
134 | ret = app; | ||
135 | c++; | ||
136 | } | ||
137 | } | ||
138 | return ret; | ||
139 | } | ||
140 | return NULL; | ||
141 | } | ||
142 | |||
143 | |||
144 | int | ||
145 | gaim_str_has_prefix (const char *s, const char *p) | ||
146 | { | ||
147 | if ((s == NULL) || (p == NULL)) | ||
148 | return 0; | ||
149 | return !strncmp (s, p, strlen (p)); | ||
150 | } | ||
151 | |||
152 | /* end of util.c */ | ||
diff --git a/src/upnp/upnp_util.h b/src/upnp/upnp_util.h new file mode 100644 index 000000000..89a8d71cf --- /dev/null +++ b/src/upnp/upnp_util.h | |||
@@ -0,0 +1,68 @@ | |||
1 | /** | ||
2 | * @file util.h Utility Functions | ||
3 | * @ingroup core | ||
4 | * | ||
5 | * gaim | ||
6 | * | ||
7 | * Gaim is the legal property of its developers, whose names are too numerous | ||
8 | * to list here. Please refer to the COPYRIGHT file distributed with this | ||
9 | * source distribution. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
24 | * | ||
25 | * @todo Rename the functions so that they live somewhere in the gaim | ||
26 | * namespace. | ||
27 | */ | ||
28 | #ifndef _GAIM_UTIL_H_ | ||
29 | #define _GAIM_UTIL_H_ | ||
30 | |||
31 | #include <stdio.h> | ||
32 | #include <string.h> | ||
33 | |||
34 | #ifdef __cplusplus | ||
35 | extern "C" | ||
36 | { | ||
37 | #endif | ||
38 | |||
39 | /** | ||
40 | * Unescapes HTML entities to their literal characters. | ||
41 | * For example "&" is replaced by '&' and so on. | ||
42 | * Actually only "&", """, "<" and ">" are currently | ||
43 | * supported. | ||
44 | * | ||
45 | * @param html The string in which to unescape any HTML entities | ||
46 | * | ||
47 | * @return the text with HTML entities literalized | ||
48 | */ | ||
49 | char *gaim_unescape_html (const char *html); | ||
50 | |||
51 | /** | ||
52 | * Compares two strings to see if the first contains the second as | ||
53 | * a proper prefix. | ||
54 | * | ||
55 | * @param s The string to check. | ||
56 | * @param p The prefix in question. | ||
57 | * | ||
58 | * @return TRUE if p is a prefix of s, otherwise FALSE. | ||
59 | */ | ||
60 | int gaim_str_has_prefix (const char *s, const char *p); | ||
61 | |||
62 | char *g_strdup_printf (const char *fmt, ...); | ||
63 | |||
64 | #ifdef __cplusplus | ||
65 | } | ||
66 | #endif | ||
67 | |||
68 | #endif | ||
diff --git a/src/upnp/upnp_xmlnode.c b/src/upnp/upnp_xmlnode.c new file mode 100644 index 000000000..b37528f00 --- /dev/null +++ b/src/upnp/upnp_xmlnode.c | |||
@@ -0,0 +1,487 @@ | |||
1 | /** | ||
2 | * @file xmlnode.c XML DOM functions | ||
3 | * | ||
4 | * gaim | ||
5 | * | ||
6 | * Gaim is the legal property of its developers, whose names are too numerous | ||
7 | * to list here. Please refer to the COPYRIGHT file distributed with this | ||
8 | * source distribution. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License | ||
21 | * along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
23 | */ | ||
24 | |||
25 | /* A lot of this code at least resembles the code in libxode, but since | ||
26 | * libxode uses memory pools that we simply have no need for, I decided to | ||
27 | * write my own stuff. Also, re-writing this lets me be as lightweight | ||
28 | * as I want to be. Thank you libxode for giving me a good starting point */ | ||
29 | |||
30 | #include "platform.h" | ||
31 | |||
32 | #include "util.h" | ||
33 | #include "gnunet_util.h" | ||
34 | #include "xmlnode.h" | ||
35 | |||
36 | #include <libxml/parser.h> | ||
37 | #include <string.h> | ||
38 | |||
39 | |||
40 | #ifdef _WIN32 | ||
41 | # define NEWLINE_S "\r\n" | ||
42 | #else | ||
43 | # define NEWLINE_S "\n" | ||
44 | #endif | ||
45 | |||
46 | #define TRUE GNUNET_YES | ||
47 | #define FALSE GNUNET_NO | ||
48 | |||
49 | #define g_return_if_fail(a) if(!(a)) return; | ||
50 | #define g_return_val_if_fail(a, val) if(!(a)) return (val); | ||
51 | |||
52 | /** | ||
53 | * The valid types for an xmlnode | ||
54 | */ | ||
55 | typedef enum _XMLNodeType | ||
56 | { | ||
57 | XMLNODE_TYPE_TAG, /**< Just a tag */ | ||
58 | XMLNODE_TYPE_ATTRIB, /**< Has attributes */ | ||
59 | XMLNODE_TYPE_DATA /**< Has data */ | ||
60 | } XMLNodeType; | ||
61 | |||
62 | typedef struct | ||
63 | { | ||
64 | xmlnode *current; | ||
65 | xmlnode **nodes; | ||
66 | unsigned int pos; | ||
67 | unsigned int size; | ||
68 | } XMLNodePool; | ||
69 | |||
70 | struct _xmlnode | ||
71 | { | ||
72 | char *name; /**< The name of the node. */ | ||
73 | char *xmlns; /**< The namespace of the node */ | ||
74 | XMLNodeType type; /**< The type of the node. */ | ||
75 | char *data; /**< The data for the node. */ | ||
76 | size_t data_sz; /**< The size of the data. */ | ||
77 | struct _xmlnode *parent; /**< The parent node or @c NULL.*/ | ||
78 | struct _xmlnode *child; /**< The child node or @c NULL.*/ | ||
79 | struct _xmlnode *lastchild; /**< The last child node or @c NULL.*/ | ||
80 | struct _xmlnode *next; /**< The next node or @c NULL. */ | ||
81 | XMLNodePool *pool; | ||
82 | int free_pool; /* set to GNUNET_YES for the root node, which must free the pool */ | ||
83 | }; | ||
84 | |||
85 | |||
86 | static void * | ||
87 | g_memdup (const void *data, size_t s) | ||
88 | { | ||
89 | void *ret; | ||
90 | |||
91 | ret = GNUNET_malloc (s); | ||
92 | memcpy (ret, data, s); | ||
93 | return ret; | ||
94 | } | ||
95 | |||
96 | static char * | ||
97 | g_string_append_len (char *prefix, const void *data, size_t s) | ||
98 | { | ||
99 | char *ret; | ||
100 | |||
101 | ret = g_strdup_printf ("%s%.*s", prefix, s, data); | ||
102 | GNUNET_free (prefix); | ||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | static xmlnode * | ||
107 | new_node (const char *name, XMLNodeType type, void *user_data) | ||
108 | { | ||
109 | xmlnode *node = GNUNET_malloc (sizeof (xmlnode)); | ||
110 | |||
111 | node->name = name == NULL ? NULL : GNUNET_strdup (name); | ||
112 | node->type = type; | ||
113 | node->pool = user_data; | ||
114 | if (node->pool->size == node->pool->pos) | ||
115 | GNUNET_array_grow (node->pool->nodes, node->pool->size, | ||
116 | node->pool->size * 2 + 64); | ||
117 | node->pool->nodes[node->pool->pos++] = node; | ||
118 | node->free_pool = 0; | ||
119 | return node; | ||
120 | } | ||
121 | |||
122 | static xmlnode * | ||
123 | xmlnode_new (const char *name, void *user_data) | ||
124 | { | ||
125 | g_return_val_if_fail (name != NULL, NULL); | ||
126 | return new_node (name, XMLNODE_TYPE_TAG, user_data); | ||
127 | } | ||
128 | |||
129 | static void | ||
130 | xmlnode_insert_child (xmlnode * parent, xmlnode * child) | ||
131 | { | ||
132 | g_return_if_fail (parent != NULL); | ||
133 | g_return_if_fail (child != NULL); | ||
134 | |||
135 | child->parent = parent; | ||
136 | if (parent->lastchild) | ||
137 | parent->lastchild->next = child; | ||
138 | else | ||
139 | parent->child = child; | ||
140 | parent->lastchild = child; | ||
141 | } | ||
142 | |||
143 | static xmlnode * | ||
144 | xmlnode_new_child (xmlnode * parent, const char *name, void *user_data) | ||
145 | { | ||
146 | xmlnode *node; | ||
147 | |||
148 | g_return_val_if_fail (parent != NULL, NULL); | ||
149 | g_return_val_if_fail (name != NULL, NULL); | ||
150 | node = new_node (name, XMLNODE_TYPE_TAG, user_data); | ||
151 | xmlnode_insert_child (parent, node); | ||
152 | return node; | ||
153 | } | ||
154 | |||
155 | static void | ||
156 | xmlnode_insert_data (xmlnode * node, | ||
157 | const char *data, int size, void *user_data) | ||
158 | { | ||
159 | xmlnode *child; | ||
160 | size_t real_size; | ||
161 | |||
162 | g_return_if_fail (node != NULL); | ||
163 | g_return_if_fail (data != NULL); | ||
164 | g_return_if_fail (size != 0); | ||
165 | real_size = size == -1 ? strlen (data) : size; | ||
166 | child = new_node (NULL, XMLNODE_TYPE_DATA, user_data); | ||
167 | child->data = g_memdup (data, real_size); | ||
168 | child->data_sz = real_size; | ||
169 | xmlnode_insert_child (node, child); | ||
170 | } | ||
171 | |||
172 | static void | ||
173 | xmlnode_remove_attrib (xmlnode * node, const char *attr) | ||
174 | { | ||
175 | xmlnode *attr_node, *sibling = NULL; | ||
176 | |||
177 | g_return_if_fail (node != NULL); | ||
178 | g_return_if_fail (attr != NULL); | ||
179 | |||
180 | for (attr_node = node->child; attr_node; attr_node = attr_node->next) | ||
181 | { | ||
182 | if (attr_node->type == XMLNODE_TYPE_ATTRIB && | ||
183 | !strcmp (attr_node->name, attr)) | ||
184 | { | ||
185 | if (node->child == attr_node) | ||
186 | { | ||
187 | node->child = attr_node->next; | ||
188 | } | ||
189 | else | ||
190 | { | ||
191 | sibling->next = attr_node->next; | ||
192 | } | ||
193 | if (node->lastchild == attr_node) | ||
194 | { | ||
195 | node->lastchild = sibling; | ||
196 | } | ||
197 | xmlnode_free (attr_node); | ||
198 | return; | ||
199 | } | ||
200 | sibling = attr_node; | ||
201 | } | ||
202 | } | ||
203 | |||
204 | static void | ||
205 | xmlnode_set_attrib (xmlnode * node, | ||
206 | const char *attr, const char *value, void *user_data) | ||
207 | { | ||
208 | xmlnode *attrib_node; | ||
209 | |||
210 | g_return_if_fail (node != NULL); | ||
211 | g_return_if_fail (attr != NULL); | ||
212 | g_return_if_fail (value != NULL); | ||
213 | xmlnode_remove_attrib (node, attr); | ||
214 | attrib_node = new_node (attr, XMLNODE_TYPE_ATTRIB, user_data); | ||
215 | attrib_node->data = GNUNET_strdup (value); | ||
216 | xmlnode_insert_child (node, attrib_node); | ||
217 | } | ||
218 | |||
219 | static void | ||
220 | xmlnode_set_namespace (xmlnode * node, const char *xmlns) | ||
221 | { | ||
222 | g_return_if_fail (node != NULL); | ||
223 | GNUNET_free_non_null (node->xmlns); | ||
224 | node->xmlns = GNUNET_strdup (xmlns); | ||
225 | } | ||
226 | |||
227 | static const char * | ||
228 | xmlnode_get_namespace (xmlnode * node) | ||
229 | { | ||
230 | g_return_val_if_fail (node != NULL, NULL); | ||
231 | return node->xmlns; | ||
232 | } | ||
233 | |||
234 | static void | ||
235 | freePool (XMLNodePool * pool) | ||
236 | { | ||
237 | unsigned int i; | ||
238 | xmlnode *x; | ||
239 | |||
240 | for (i = 0; i < pool->pos; i++) | ||
241 | { | ||
242 | x = pool->nodes[i]; | ||
243 | GNUNET_free_non_null (x->name); | ||
244 | GNUNET_free_non_null (x->data); | ||
245 | GNUNET_free_non_null (x->xmlns); | ||
246 | GNUNET_free (x); | ||
247 | } | ||
248 | GNUNET_array_grow (pool->nodes, pool->size, 0); | ||
249 | GNUNET_free (pool); | ||
250 | } | ||
251 | |||
252 | void | ||
253 | xmlnode_free (xmlnode * node) | ||
254 | { | ||
255 | g_return_if_fail (node != NULL); | ||
256 | if (node->free_pool != GNUNET_YES) | ||
257 | return; | ||
258 | freePool (node->pool); | ||
259 | } | ||
260 | |||
261 | static xmlnode * | ||
262 | xmlnode_get_child_with_namespace (const xmlnode * parent, | ||
263 | const char *name, const char *ns) | ||
264 | { | ||
265 | xmlnode *x; | ||
266 | xmlnode *ret = NULL; | ||
267 | char *parent_name; | ||
268 | char *child_name; | ||
269 | |||
270 | if (parent == NULL) | ||
271 | return NULL; | ||
272 | if (name == NULL) | ||
273 | return NULL; | ||
274 | |||
275 | parent_name = GNUNET_strdup (name); | ||
276 | child_name = strstr (parent_name, "/"); | ||
277 | if (child_name != NULL) | ||
278 | { | ||
279 | child_name[0] = '\0'; | ||
280 | child_name++; | ||
281 | } | ||
282 | |||
283 | for (x = parent->child; x; x = x->next) | ||
284 | { | ||
285 | const char *xmlns = NULL; | ||
286 | if (ns) | ||
287 | xmlns = xmlnode_get_namespace (x); | ||
288 | |||
289 | if (x->type == XMLNODE_TYPE_TAG && name | ||
290 | && !strcmp (parent_name, x->name) && (!ns | ||
291 | || (xmlns | ||
292 | && !strcmp (ns, xmlns)))) | ||
293 | { | ||
294 | ret = x; | ||
295 | break; | ||
296 | } | ||
297 | } | ||
298 | |||
299 | if (child_name && ret) | ||
300 | ret = xmlnode_get_child (ret, child_name); | ||
301 | |||
302 | GNUNET_free (parent_name); | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | xmlnode * | ||
307 | xmlnode_get_child (const xmlnode * parent, const char *name) | ||
308 | { | ||
309 | return xmlnode_get_child_with_namespace (parent, name, NULL); | ||
310 | } | ||
311 | |||
312 | char * | ||
313 | xmlnode_get_data (xmlnode * node) | ||
314 | { | ||
315 | char *str = NULL; | ||
316 | xmlnode *c; | ||
317 | |||
318 | if (node == NULL) | ||
319 | return NULL; | ||
320 | for (c = node->child; c; c = c->next) | ||
321 | { | ||
322 | if (c->type == XMLNODE_TYPE_DATA) | ||
323 | { | ||
324 | if (!str) | ||
325 | str = GNUNET_strdup (""); | ||
326 | str = g_string_append_len (str, c->data, c->data_sz); | ||
327 | } | ||
328 | } | ||
329 | if (str == NULL) | ||
330 | return NULL; | ||
331 | |||
332 | return str; | ||
333 | } | ||
334 | |||
335 | static void | ||
336 | xmlnode_parser_element_start_libxml (void *user_data, | ||
337 | const xmlChar * element_name, | ||
338 | const xmlChar * prefix, | ||
339 | const xmlChar * xmlns, | ||
340 | int nb_namespaces, | ||
341 | const xmlChar ** namespaces, | ||
342 | int nb_attributes, | ||
343 | int nb_defaulted, | ||
344 | const xmlChar ** attributes) | ||
345 | { | ||
346 | XMLNodePool *xpd = user_data; | ||
347 | xmlnode *node; | ||
348 | int i; | ||
349 | |||
350 | if (!element_name) | ||
351 | return; | ||
352 | if (xpd->current) | ||
353 | node = | ||
354 | xmlnode_new_child (xpd->current, (const char *) element_name, | ||
355 | user_data); | ||
356 | else | ||
357 | node = xmlnode_new ((const char *) element_name, user_data); | ||
358 | |||
359 | xmlnode_set_namespace (node, (const char *) xmlns); | ||
360 | |||
361 | for (i = 0; i < nb_attributes * 5; i += 5) | ||
362 | { | ||
363 | char *txt; | ||
364 | int attrib_len = attributes[i + 4] - attributes[i + 3]; | ||
365 | char *attrib = GNUNET_malloc (attrib_len + 1); | ||
366 | memcpy (attrib, attributes[i + 3], attrib_len); | ||
367 | attrib[attrib_len] = '\0'; | ||
368 | txt = attrib; | ||
369 | attrib = gaim_unescape_html (txt); | ||
370 | GNUNET_free (txt); | ||
371 | xmlnode_set_attrib (node, (const char *) attributes[i], attrib, | ||
372 | user_data); | ||
373 | GNUNET_free (attrib); | ||
374 | } | ||
375 | xpd->current = node; | ||
376 | } | ||
377 | |||
378 | static void | ||
379 | xmlnode_parser_element_end_libxml (void *user_data, | ||
380 | const xmlChar * element_name, | ||
381 | const xmlChar * prefix, | ||
382 | const xmlChar * xmlns) | ||
383 | { | ||
384 | XMLNodePool *xpd = user_data; | ||
385 | |||
386 | if (!element_name || !xpd->current) | ||
387 | return; | ||
388 | if (xpd->current->parent) | ||
389 | { | ||
390 | if (!xmlStrcmp ((xmlChar *) xpd->current->name, element_name)) | ||
391 | xpd->current = xpd->current->parent; | ||
392 | } | ||
393 | } | ||
394 | |||
395 | static void | ||
396 | xmlnode_parser_element_text_libxml (void *user_data, | ||
397 | const xmlChar * text, int text_len) | ||
398 | { | ||
399 | XMLNodePool *xpd = user_data; | ||
400 | |||
401 | if (!xpd->current || !text || !text_len) | ||
402 | return; | ||
403 | xmlnode_insert_data (xpd->current, | ||
404 | (const char *) text, text_len, user_data); | ||
405 | } | ||
406 | |||
407 | static xmlSAXHandler xmlnode_parser_libxml = { | ||
408 | .internalSubset = NULL, | ||
409 | .isStandalone = NULL, | ||
410 | .hasInternalSubset = NULL, | ||
411 | .hasExternalSubset = NULL, | ||
412 | .resolveEntity = NULL, | ||
413 | .getEntity = NULL, | ||
414 | .entityDecl = NULL, | ||
415 | .notationDecl = NULL, | ||
416 | .attributeDecl = NULL, | ||
417 | .elementDecl = NULL, | ||
418 | .unparsedEntityDecl = NULL, | ||
419 | .setDocumentLocator = NULL, | ||
420 | .startDocument = NULL, | ||
421 | .endDocument = NULL, | ||
422 | .startElement = NULL, | ||
423 | .endElement = NULL, | ||
424 | .reference = NULL, | ||
425 | .characters = xmlnode_parser_element_text_libxml, | ||
426 | .ignorableWhitespace = NULL, | ||
427 | .processingInstruction = NULL, | ||
428 | .comment = NULL, | ||
429 | .warning = NULL, | ||
430 | .error = NULL, | ||
431 | .fatalError = NULL, | ||
432 | .getParameterEntity = NULL, | ||
433 | .cdataBlock = NULL, | ||
434 | .externalSubset = NULL, | ||
435 | .initialized = XML_SAX2_MAGIC, | ||
436 | ._private = NULL, | ||
437 | .startElementNs = xmlnode_parser_element_start_libxml, | ||
438 | .endElementNs = xmlnode_parser_element_end_libxml, | ||
439 | .serror = NULL | ||
440 | }; | ||
441 | |||
442 | xmlnode * | ||
443 | xmlnode_from_str (const char *str, int size) | ||
444 | { | ||
445 | XMLNodePool *xpd; | ||
446 | xmlnode *ret; | ||
447 | size_t real_size; | ||
448 | |||
449 | g_return_val_if_fail (str != NULL, NULL); | ||
450 | |||
451 | real_size = size < 0 ? strlen (str) : size; | ||
452 | xpd = GNUNET_malloc (sizeof (XMLNodePool)); | ||
453 | memset (xpd, 0, sizeof (XMLNodePool)); | ||
454 | if (xmlSAXUserParseMemory (&xmlnode_parser_libxml, xpd, str, real_size) < 0) | ||
455 | { | ||
456 | freePool (xpd); | ||
457 | return NULL; | ||
458 | } | ||
459 | ret = xpd->current; | ||
460 | ret->free_pool = GNUNET_YES; | ||
461 | return ret; | ||
462 | } | ||
463 | |||
464 | xmlnode * | ||
465 | xmlnode_get_next_twin (xmlnode * node) | ||
466 | { | ||
467 | xmlnode *sibling; | ||
468 | const char *ns = xmlnode_get_namespace (node); | ||
469 | |||
470 | g_return_val_if_fail (node != NULL, NULL); | ||
471 | g_return_val_if_fail (node->type == XMLNODE_TYPE_TAG, NULL); | ||
472 | |||
473 | for (sibling = node->next; sibling; sibling = sibling->next) | ||
474 | { | ||
475 | const char *xmlns = NULL; | ||
476 | if (ns) | ||
477 | xmlns = xmlnode_get_namespace (sibling); | ||
478 | |||
479 | if (sibling->type == XMLNODE_TYPE_TAG | ||
480 | && !strcmp (node->name, sibling->name) && (!ns | ||
481 | || (xmlns | ||
482 | && !strcmp (ns, | ||
483 | xmlns)))) | ||
484 | return sibling; | ||
485 | } | ||
486 | return NULL; | ||
487 | } | ||
diff --git a/src/upnp/upnp_xmlnode.h b/src/upnp/upnp_xmlnode.h new file mode 100644 index 000000000..199975e37 --- /dev/null +++ b/src/upnp/upnp_xmlnode.h | |||
@@ -0,0 +1,92 @@ | |||
1 | /** | ||
2 | * @file xmlnode.h XML DOM functions | ||
3 | * @ingroup core | ||
4 | * | ||
5 | * gaim | ||
6 | * | ||
7 | * Gaim is the legal property of its developers, whose names are too numerous | ||
8 | * to list here. Please refer to the COPYRIGHT file distributed with this | ||
9 | * source distribution. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
24 | */ | ||
25 | #ifndef _GGAIM_XMLNODE_H_ | ||
26 | #define _GGAIM_XMLNODE_H_ | ||
27 | |||
28 | |||
29 | #ifdef __cplusplus | ||
30 | extern "C" | ||
31 | { | ||
32 | #endif | ||
33 | |||
34 | /** | ||
35 | * An xmlnode. | ||
36 | */ | ||
37 | typedef struct _xmlnode xmlnode; | ||
38 | |||
39 | /** | ||
40 | * Gets a child node named name. | ||
41 | * | ||
42 | * @param parent The parent node. | ||
43 | * @param name The child's name. | ||
44 | * | ||
45 | * @return The child or NULL. | ||
46 | */ | ||
47 | xmlnode *xmlnode_get_child (const xmlnode * parent, const char *name); | ||
48 | |||
49 | /** | ||
50 | * Gets the next node with the same name as node. | ||
51 | * | ||
52 | * @param node The node of a twin to find. | ||
53 | * | ||
54 | * @return The twin of node or NULL. | ||
55 | */ | ||
56 | xmlnode *xmlnode_get_next_twin (xmlnode * node); | ||
57 | |||
58 | /** | ||
59 | * Gets data from a node. | ||
60 | * | ||
61 | * @param node The node to get data from. | ||
62 | * | ||
63 | * @return The data from the node. You must g_free | ||
64 | * this string when finished using it. | ||
65 | */ | ||
66 | char *xmlnode_get_data (xmlnode * node); | ||
67 | |||
68 | /** | ||
69 | * Creates a node from a string of XML. Calling this on the | ||
70 | * root node of an XML document will parse the entire document | ||
71 | * into a tree of nodes, and return the xmlnode of the root. | ||
72 | * | ||
73 | * @param str The string of xml. | ||
74 | * @param size The size of the string, or -1 if @a str is | ||
75 | * NUL-terminated. | ||
76 | * | ||
77 | * @return The new node. | ||
78 | */ | ||
79 | xmlnode *xmlnode_from_str (const char *str, int size); | ||
80 | |||
81 | /** | ||
82 | * Frees a node and all of it's children. | ||
83 | * | ||
84 | * @param node The node to free. | ||
85 | */ | ||
86 | void xmlnode_free (xmlnode * node); | ||
87 | |||
88 | #ifdef __cplusplus | ||
89 | } | ||
90 | #endif | ||
91 | |||
92 | #endif /* _GAIM_XMLNODE_H_ */ | ||