]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/LegacyNATTraversal.c
mDNSResponder-214.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / LegacyNATTraversal.c
1 /* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16
17 Change History (most recent first):
18
19 $Log: LegacyNATTraversal.c,v $
20 Revision 1.65 2009/07/03 03:16:07 jessic2
21 <rdar://problem/7026146> BTMM: UPnP works in Leopard but doesn't work in SnowLeopard (URLBase is empty) Made changes to support the case where the URLBase tag exists but there isn't a valid URL
22
23 Revision 1.64 2009/06/25 21:07:44 herscher
24 <rdar://problem/4147784> B4W should support UPnP
25
26 Revision 1.63 2009/03/26 03:59:00 jessic2
27 Changes for <rdar://problem/6492552&6492593&6492609&6492613&6492628&6492640&6492699>
28
29 Revision 1.62 2009/02/13 06:31:09 cheshire
30 Converted LogOperation messages to LogInfo
31
32 Revision 1.61 2009/01/23 19:25:43 mcguire
33 <rdar://problem/6514439> UPnP: Should not use NATErr_Refused when too many conflict retries
34
35 Revision 1.60 2009/01/23 00:38:36 mcguire
36 <rdar://problem/5570906> BTMM: Doesn't work with Linksys WRT54GS firmware 4.71.1
37
38 Revision 1.59 2009/01/22 20:32:17 mcguire
39 <rdar://problem/6446934> BTMM: pref pane reports enabled but negotiation failed
40 Make sure we push the pointer out past the LF if we read it.
41
42 Revision 1.58 2009/01/22 01:15:58 mcguire
43 <rdar://problem/6446934> BTMM: pref pane reports enabled but negotiation failed
44
45 Revision 1.57 2008/12/19 21:09:22 mcguire
46 <rdar://problem/6431147> UPnP: error messages when canceling seemingly unrelated browse
47
48 Revision 1.56 2008/12/06 01:42:57 mcguire
49 <rdar://problem/6418958> Need to exponentially back-off after failure to get public address
50
51 Revision 1.55 2008/12/01 19:43:48 mcguire
52 <rdar://problem/6404766> UPnP: Handle errorCode 718 as a conflict when requesting a port mapping
53
54 Revision 1.54 2008/11/26 20:57:37 cheshire
55 For consistency with other similar macros, renamed mdnsIsDigit/mdnsIsLetter/mdnsValidHostChar
56 to mDNSIsDigit/mDNSIsLetter/mDNSValidHostChar
57
58 Revision 1.53 2008/11/26 20:34:04 cheshire
59 Changed "destroying SSDPSocket" LogOperation debugging messages to debugf
60
61 Revision 1.52 2008/11/26 19:54:03 cheshire
62 Changed some "LogOperation" debugging messages to "debugf"
63
64 Revision 1.51 2008/11/20 02:23:38 mcguire
65 <rdar://problem/6041208> need to handle URLBase
66
67 Revision 1.50 2008/09/20 00:34:22 mcguire
68 <rdar://problem/6129039> BTMM: Add support for WANPPPConnection
69
70 Revision 1.49 2008/08/07 21:51:13 mcguire
71 <rdar://problem/5904423> UPnP: Possible memory corruption bug
72 <rdar://problem/5930173> UPnP: Combine URL parsing code
73
74 Revision 1.48 2008/07/24 20:23:04 cheshire
75 <rdar://problem/3988320> Should use randomized source ports and transaction IDs to avoid DNS cache poisoning
76
77 Revision 1.47 2008/07/18 21:37:46 mcguire
78 <rdar://problem/5736845> BTMM: alternate SSDP queries between multicast & unicast
79
80 Revision 1.46 2008/05/13 01:51:12 mcguire
81 <rdar://problem/5839161> UPnP compatibility workaround for Netgear WGT624
82
83 Revision 1.45 2007/12/06 00:22:27 mcguire
84 <rdar://problem/5604567> BTMM: Doesn't work with Linksys WAG300N 1.01.06 (sending from 1026/udp)
85
86 Revision 1.44 2007/11/02 20:45:40 cheshire
87 Don't log "connection failed" in customer builds
88
89 Revision 1.43 2007/10/18 20:09:47 cheshire
90 <rdar://problem/5545930> BTMM: Back to My Mac not working with D-Link DGL-4100 NAT gateway
91
92 Revision 1.42 2007/10/16 17:37:18 cheshire
93 <rdar://problem/3557903> Performance: Core code will not work on platforms with small stacks
94 Cut SendSOAPMsgControlAction stack from 2144 to 96 bytes
95
96 Revision 1.41 2007/10/15 23:02:00 cheshire
97 Off-by-one error: Incorrect trailing zero byte on the end of the SSDP Discovery message
98
99 Revision 1.40 2007/09/20 21:41:49 cheshire
100 <rdar://problem/5495568> Legacy NAT Traversal - unmap request failed with error -65549
101
102 Revision 1.39 2007/09/20 20:41:40 cheshire
103 Reordered functions in file, in preparation for following fix
104
105 Revision 1.38 2007/09/18 21:42:30 cheshire
106 To reduce programming mistakes, renamed ExtPort to RequestedPort
107
108 Revision 1.37 2007/09/14 21:26:09 cheshire
109 <rdar://problem/5482627> BTMM: Need to manually avoid port conflicts when using UPnP gateways
110
111 Revision 1.36 2007/09/14 01:15:50 cheshire
112 Minor fixes for problems discovered in pre-submission testing
113
114 Revision 1.35 2007/09/13 00:16:42 cheshire
115 <rdar://problem/5468706> Miscellaneous NAT Traversal improvements
116
117 Revision 1.34 2007/09/12 23:03:08 cheshire
118 <rdar://problem/5476978> DNSServiceNATPortMappingCreate callback not giving correct interface index
119
120 Revision 1.33 2007/09/12 19:22:20 cheshire
121 Variable renaming in preparation for upcoming fixes e.g. priv/pub renamed to intport/extport
122 Made NAT Traversal packet handlers take typed data instead of anonymous "mDNSu8 *" byte pointers
123
124 Revision 1.32 2007/09/11 19:19:16 cheshire
125 Correct capitalization of "uPNP" to "UPnP"
126
127 Revision 1.31 2007/09/10 22:14:16 cheshire
128 When constructing fake NATAddrReply or NATPortMapReply packet, need to calculate
129 plausible upseconds value or core logic will think NAT engine has been rebooted
130
131 Revision 1.30 2007/09/05 20:46:17 cheshire
132 Tidied up alignment of code layout
133
134 Revision 1.29 2007/08/03 20:18:01 vazquez
135 <rdar://problem/5382177> LegacyNATTraversal: reading out of bounds can lead to DoS
136
137 Revision 1.28 2007/07/31 02:28:36 vazquez
138 <rdar://problem/3734269> NAT-PMP: Detect public IP address changes and base station reboot
139
140 Revision 1.27 2007/07/30 23:17:03 vazquez
141 Since lease times are meaningless in UPnP, return NATMAP_DEFAULT_LEASE in UPnP port mapping reply
142
143 Revision 1.26 2007/07/27 22:50:08 vazquez
144 Allocate memory for UPnP request and reply buffers instead of using arrays
145
146 Revision 1.25 2007/07/27 20:33:44 vazquez
147 Make sure we clean up previous port mapping requests before starting an unmap
148
149 Revision 1.24 2007/07/27 00:57:48 vazquez
150 If a tcp connection is already established for doing a port mapping, don't start it again
151
152 Revision 1.23 2007/07/26 21:19:26 vazquez
153 Retry port mapping with incremented port number (up to max) in order to handle
154 port mapping conflicts on UPnP gateways
155
156 Revision 1.22 2007/07/25 21:41:00 vazquez
157 Make sure we clean up opened sockets when there are network transitions and when changing
158 port mappings
159
160 Revision 1.21 2007/07/25 03:05:03 vazquez
161 Fixes for:
162 <rdar://problem/5338913> LegacyNATTraversal: UPnP heap overflow
163 <rdar://problem/5338933> LegacyNATTraversal: UPnP stack buffer overflow
164 and a myriad of other security problems
165
166 Revision 1.20 2007/07/16 20:15:10 vazquez
167 <rdar://problem/3867231> LegacyNATTraversal: Need complete rewrite
168
169 Revision 1.19 2007/06/21 16:37:43 jgraessley
170 Bug #: 5280520
171 Reviewed by: Stuart Cheshire
172 Additional changes to get this compiling on the embedded platform.
173
174 Revision 1.18 2007/05/09 01:43:32 cheshire
175 <rdar://problem/5187028> Change sprintf and strcpy to their safer snprintf and strlcpy equivalents
176
177 Revision 1.17 2007/02/27 02:48:25 cheshire
178 Parameter to LNT_GetPublicIP function is IPv4 address, not anonymous "mDNSOpaque32" object
179
180 Revision 1.16 2006/08/14 23:24:39 cheshire
181 Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
182
183 Revision 1.15 2006/07/05 23:30:57 cheshire
184 Rename LegacyNATInit() -> LNT_Init()
185
186 Revision 1.14 2005/12/08 03:00:33 cheshire
187 <rdar://problem/4349971> Byte order bugs in Legacy NAT traversal code
188
189 Revision 1.13 2005/09/07 18:23:05 ksekar
190 <rdar://problem/4151514> Off-by-one overflow in LegacyNATTraversal
191
192 Revision 1.12 2005/07/22 21:36:16 ksekar
193 Fix GCC 4.0/Intel compiler warnings
194
195 Revision 1.11 2004/12/03 03:34:20 ksekar
196 <rdar://problem/3882674> LegacyNATTraversal.c leaks threads
197
198 Revision 1.10 2004/12/01 02:43:49 cheshire
199 Update copyright message
200
201 Revision 1.9 2004/10/27 02:25:05 cheshire
202 <rdar://problem/3816029> Random memory smashing bug
203
204 Revision 1.8 2004/10/27 02:17:21 cheshire
205 Turn off "safe_close: ERROR" error messages -- there are too many of them
206
207 Revision 1.7 2004/10/26 21:15:40 cheshire
208 <rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0
209 Additional fixes: Code should set fds to -1 after closing sockets.
210
211 Revision 1.6 2004/10/26 20:59:20 cheshire
212 <rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0
213
214 Revision 1.5 2004/10/26 01:01:35 cheshire
215 Use "#if 0" instead of commenting out code
216
217 Revision 1.4 2004/10/10 06:51:36 cheshire
218 Declared some strings "const" as appropriate
219
220 Revision 1.3 2004/09/21 23:40:12 ksekar
221 <rdar://problem/3810349> mDNSResponder to return errors on NAT traversal failure
222
223 Revision 1.2 2004/09/17 01:08:52 cheshire
224 Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h
225 The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces
226 declared in that file are ONLY appropriate to single-address-space embedded applications.
227 For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used.
228
229 Revision 1.1 2004/08/18 17:35:41 ksekar
230 <rdar://problem/3651443>: Feature #9586: Need support for Legacy NAT gateways
231 */
232
233 #ifdef _LEGACY_NAT_TRAVERSAL_
234
235 #include "stdlib.h" // For strtol()
236 #include "string.h" // For strlcpy(), For strncpy(), strncasecmp()
237
238 #if defined( WIN32 )
239 # include <winsock2.h>
240 # include <ws2tcpip.h>
241 # define strcasecmp _stricmp
242 # define strncasecmp _strnicmp
243 # define mDNSASLLog( UUID, SUBDOMAIN, RESULT, SIGNATURE, FORMAT, ... ) ;
244
245 static int
246 inet_pton( int family, const char * addr, void * dst )
247 {
248 struct sockaddr_storage ss;
249 int sslen = sizeof( ss );
250
251 ZeroMemory( &ss, sizeof( ss ) );
252 ss.ss_family = family;
253
254 if ( WSAStringToAddressA( addr, family, NULL, ( struct sockaddr* ) &ss, &sslen ) == 0 )
255 {
256 if ( family == AF_INET ) { memcpy( dst, &( ( struct sockaddr_in* ) &ss)->sin_addr, sizeof( IN_ADDR ) ); return 1; }
257 else if ( family == AF_INET6 ) { memcpy( dst, &( ( struct sockaddr_in6* ) &ss)->sin6_addr, sizeof( IN6_ADDR ) ); return 1; }
258 else return 0;
259 }
260 else return 0;
261 }
262 #else
263 # include <arpa/inet.h> // For inet_pton()
264 #endif
265
266 #include "mDNSEmbeddedAPI.h"
267 #include "uDNS.h" // For natTraversalHandleAddressReply() etc.
268
269 // used to format SOAP port mapping arguments
270 typedef struct Property_struct
271 {
272 char *name;
273 char *type;
274 char *value;
275 } Property;
276
277 // All of the text parsing in this file is intentionally transparent so that we know exactly
278 // what's being done to the text, with an eye towards preventing security problems.
279
280 // This is an evolving list of useful acronyms to know. Please add to it at will.
281 // ST Service Type
282 // NT Notification Type
283 // USN Unique Service Name
284 // UDN Unique Device Name
285 // UUID Universally Unique Identifier
286 // URN/urn Universal Resource Name
287
288 // Forward declaration because of circular reference:
289 // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse
290 // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again
291 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n);
292
293 #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (n)->tcpInfo.retries)
294
295 // Note that this function assumes src is already NULL terminated
296 mDNSlocal void AllocAndCopy(mDNSu8** dst, mDNSu8* src)
297 {
298 if (src == mDNSNULL) return;
299 if ((*dst = (mDNSu8 *) mDNSPlatformMemAllocate(strlen((char*)src) + 1)) == mDNSNULL) { LogMsg("AllocAndCopy: can't allocate string"); return; }
300 strcpy((char *)*dst, (char*)src);
301 }
302
303 // This function does a simple parse of an HTTP URL that may include a hostname, port, and path
304 // If found in the URL, addressAndPort and path out params will point to newly allocated space (and will leak if they were previously pointing at allocated space)
305 mDNSlocal mStatus ParseHttpUrl(char* ptr, char* end, mDNSu8** addressAndPort, mDNSIPPort* port, mDNSu8** path)
306 {
307 // if the data begins with "http://", we assume there is a hostname and possibly a port number
308 if (end - ptr >= 7 && strncasecmp(ptr, "http://", 7) == 0)
309 {
310 int i;
311 char* stop = end;
312 char* addrPtr = mDNSNULL;
313
314 ptr += 7; //skip over "http://"
315 if (ptr >= end) { LogInfo("ParseHttpUrl: past end of buffer parsing host:port"); return mStatus_BadParamErr; }
316
317 // find the end of the host:port
318 addrPtr = ptr;
319 for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break;
320
321 // allocate the buffer (len i+1 so we have space to terminate the string)
322 if ((*addressAndPort = (mDNSu8 *) mDNSPlatformMemAllocate(i+1)) == mDNSNULL) { LogMsg("ParseHttpUrl: can't allocate address string"); return mStatus_NoMemoryErr; }
323 strncpy((char *)*addressAndPort, ptr, i);
324 (*addressAndPort)[i] = '\0';
325
326 // find the port number in the string, by looking backwards for the ':'
327 stop = ptr; // can't go back farther than the original start
328 ptr = addrPtr; // move ptr to the path part
329
330 for (addrPtr--;addrPtr>stop;addrPtr--)
331 {
332 if (*addrPtr == ':')
333 {
334 int tmpport;
335 addrPtr++; // skip over ':'
336 tmpport = (int)strtol(addrPtr, mDNSNULL, 10);
337 *port = mDNSOpaque16fromIntVal(tmpport); // store it properly converted
338 break;
339 }
340 }
341 }
342
343 // ptr should now point to the first character we haven't yet processed
344 // everything that remains is the path
345 if (path && ptr < end)
346 {
347 if ((*path = (mDNSu8 *)mDNSPlatformMemAllocate(end - ptr + 1)) == mDNSNULL) { LogMsg("ParseHttpUrl: can't mDNSPlatformMemAllocate path"); return mStatus_NoMemoryErr; }
348 strncpy((char *)*path, ptr, end - ptr);
349 (*path)[end - ptr] = '\0';
350 }
351
352 return mStatus_NoError;
353 }
354
355 enum
356 {
357 HTTPCode_NeedMoreData = -1, // No code found in stream
358 HTTPCode_Other = -2, // Valid code other than those below found in stream
359 HTTPCode_Bad = -3,
360 HTTPCode_200 = 200,
361 HTTPCode_404 = 404,
362 HTTPCode_500 = 500,
363 };
364
365 mDNSlocal mDNSs16 ParseHTTPResponseCode(mDNSu8** data, mDNSu8* end)
366 {
367 mDNSu8* ptr = *data;
368 char * code;
369
370 if (end - ptr < 5) return HTTPCode_NeedMoreData;
371 if (strncasecmp((char*)ptr, "HTTP/", 5) != 0) return HTTPCode_Bad;
372 ptr += 5;
373 // should we care about the HTTP protocol version?
374
375 // look for first space, which must come before first LF
376 while (ptr && ptr != end)
377 {
378 if (*ptr == '\n') return HTTPCode_Bad;
379 if (*ptr == ' ') break;
380 ptr++;
381 }
382 if (ptr == end) return HTTPCode_NeedMoreData;
383 ptr++;
384
385 if (end - ptr < 3) return HTTPCode_NeedMoreData;
386
387 code = (char*)ptr;
388 ptr += 3;
389 while (ptr && ptr != end)
390 {
391 if (*ptr == '\n') break;
392 ptr++;
393 }
394 if (ptr == end) return HTTPCode_NeedMoreData;
395 *data = ++ptr;
396
397 if (memcmp(code, "200", 3) == 0) return HTTPCode_200;
398 if (memcmp(code, "404", 3) == 0) return HTTPCode_404;
399 if (memcmp(code, "500", 3) == 0) return HTTPCode_500;
400
401 LogInfo("ParseHTTPResponseCode found unexpected result code: %c%c%c", code[0], code[1], code[2]);
402 return HTTPCode_Other;
403 }
404
405 // This function parses the xml body of the device description response from the router. Basically, we look to make sure this is a response
406 // referencing a service we care about (WANIPConnection or WANPPPConnection), look for the "controlURL" header immediately following, and copy the addressing and URL info we need
407 mDNSlocal void handleLNTDeviceDescriptionResponse(tcpLNTInfo *tcpInfo)
408 {
409 mDNS *m = tcpInfo->m;
410 char *ptr = (char *)tcpInfo->Reply;
411 char *end = (char *)tcpInfo->Reply + tcpInfo->nread;
412 char *stop = mDNSNULL;
413 mDNSs16 http_result;
414
415 if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return; // already have the info we need
416
417 http_result = ParseHTTPResponseCode((mDNSu8**)&ptr, (mDNSu8*)end); // Note: modifies ptr
418 if (http_result == HTTPCode_404) LNT_ClearState(m);
419 if (http_result != HTTPCode_200)
420 {
421 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "noop", "HTTP Result", "HTTP code: %d", http_result);
422 return;
423 }
424
425 // Always reset our flag to use WANIPConnection. We'll use WANPPPConnection if we find it and don't find WANIPConnection.
426 m->UPnPWANPPPConnection = mDNSfalse;
427
428 // find either service we care about
429 while (ptr && ptr < end)
430 {
431 if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break;
432 ptr++;
433 }
434 if (ptr == end)
435 {
436 ptr = (char *)tcpInfo->Reply;
437 while (ptr && ptr < end)
438 {
439 if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0))
440 {
441 m->UPnPWANPPPConnection = mDNStrue;
442 break;
443 }
444 ptr++;
445 }
446 }
447 if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; }
448
449 // find "controlURL", starting from where we left off
450 while (ptr && ptr < end)
451 {
452 if (*ptr == 'c' && (strncasecmp(ptr, "controlURL", 10) == 0)) break; // find the first 'c'; is this controlURL? if not, keep looking
453 ptr++;
454 }
455 if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; }
456 ptr += 11; // skip over "controlURL>"
457 if (ptr >= end) { LogInfo("handleLNTDeviceDescriptionResponse: past end of buffer and no body!"); return; } // check ptr again in case we skipped over the end of the buffer
458
459 // find the end of the controlURL element
460 for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } }
461
462 // fill in default port
463 m->UPnPSOAPPort = m->UPnPRouterPort;
464
465 // free string pointers and set to NULL
466 if (m->UPnPSOAPAddressString != mDNSNULL)
467 {
468 mDNSPlatformMemFree(m->UPnPSOAPAddressString);
469 m->UPnPSOAPAddressString = mDNSNULL;
470 }
471 if (m->UPnPSOAPURL != mDNSNULL)
472 {
473 mDNSPlatformMemFree(m->UPnPSOAPURL);
474 m->UPnPSOAPURL = mDNSNULL;
475 }
476
477 if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, &m->UPnPSOAPURL) != mStatus_NoError) return;
478 // the SOAPURL should look something like "/uuid:0013-108c-4b3f0000f3dc"
479
480 if (m->UPnPSOAPAddressString == mDNSNULL)
481 {
482 ptr = (char *)tcpInfo->Reply;
483 while (ptr && ptr < end)
484 {
485 if (*ptr == 'U' && (strncasecmp(ptr, "URLBase", 7) == 0)) break;
486 ptr++;
487 }
488
489 if (ptr < end) // found URLBase
490 {
491 LogInfo("handleLNTDeviceDescriptionResponse: found URLBase");
492 ptr += 8; // skip over "URLBase>"
493 // find the end of the URLBase element
494 for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } }
495 if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, mDNSNULL) != mStatus_NoError)
496 {
497 LogInfo("handleLNTDeviceDescriptionResponse: failed to parse URLBase");
498 }
499 }
500
501 // if all else fails, use the router address string
502 if (m->UPnPSOAPAddressString == mDNSNULL) AllocAndCopy(&m->UPnPSOAPAddressString, m->UPnPRouterAddressString);
503 }
504 if (m->UPnPSOAPAddressString == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPAddressString is NULL");
505 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m->UPnPSOAPAddressString);
506
507 if (m->UPnPSOAPURL == mDNSNULL) AllocAndCopy(&m->UPnPSOAPURL, m->UPnPRouterURL);
508 if (m->UPnPSOAPURL == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPURL is NULL");
509 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP URL [%s]", m->UPnPSOAPURL);
510 }
511
512 mDNSlocal void handleLNTGetExternalAddressResponse(tcpLNTInfo *tcpInfo)
513 {
514 mDNS *m = tcpInfo->m;
515 mDNSu16 err = NATErr_None;
516 mDNSv4Addr ExtAddr;
517 mDNSu8 *ptr = (mDNSu8*)tcpInfo->Reply;
518 mDNSu8 *end = (mDNSu8*)tcpInfo->Reply + tcpInfo->nread;
519 mDNSu8 *addrend;
520 static char tagname[20] = "NewExternalIPAddress"; // Array NOT including a terminating nul
521
522 // LogInfo("handleLNTGetExternalAddressResponse: %s", ptr);
523
524 mDNSs16 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
525 if (http_result == HTTPCode_404) LNT_ClearState(m);
526 if (http_result != HTTPCode_200)
527 {
528 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "HTTP Result", "HTTP code: %d", http_result);
529 return;
530 }
531
532
533 while (ptr < end && strncasecmp((char*)ptr, tagname, sizeof(tagname))) ptr++;
534 ptr += sizeof(tagname); // Skip over "NewExternalIPAddress"
535 while (ptr < end && *ptr != '>') ptr++;
536 ptr += 1; // Skip over ">"
537 // Find the end of the address and terminate the string so inet_pton() can convert it
538 addrend = ptr;
539 while (addrend < end && (mDNSIsDigit(*addrend) || *addrend == '.')) addrend++;
540 if (addrend >= end) return;
541 *addrend = 0;
542
543 if (inet_pton(AF_INET, (char*)ptr, &ExtAddr) <= 0)
544 {
545 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "inet_pton", "");
546 LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr);
547 err = NATErr_NetFail;
548 ExtAddr = zerov4Addr;
549 }
550 if (!err) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr);
551
552 natTraversalHandleAddressReply(m, err, ExtAddr);
553 }
554
555 mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo)
556 {
557 mDNS *m = tcpInfo->m;
558 mDNSIPPort extport = zeroIPPort;
559 mDNSu8 *ptr = (mDNSu8*)tcpInfo->Reply;
560 mDNSu8 *end = (mDNSu8*)tcpInfo->Reply + tcpInfo->nread;
561 NATTraversalInfo *natInfo;
562 mDNSs16 http_result;
563
564 for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break; }
565
566 if (!natInfo) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; }
567
568 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
569 if (http_result == HTTPCode_200)
570 {
571 LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)",
572 mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries);
573
574 // Make sure to compute extport *before* we zero tcpInfo->retries
575 extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo));
576 tcpInfo->retries = 0;
577 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE);
578 }
579 else if (http_result == HTTPCode_500)
580 {
581 while (ptr && ptr != end)
582 {
583 if (((*ptr == 'c' || *ptr == 'C') && end - ptr >= 8 && strncasecmp((char*)ptr, "Conflict", 8) == 0) || (*ptr == '>' && end - ptr >= 15 && strncasecmp((char*)ptr, ">718</errorCode", 15) == 0))
584 {
585 if (tcpInfo->retries < 100)
586 {
587 tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo);
588 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict", "Retry %d", tcpInfo->retries);
589 }
590 else
591 {
592 LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort));
593 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict - too many retries", "Retries: %d", tcpInfo->retries);
594 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0);
595 }
596 return;
597 }
598 ptr++;
599 }
600 }
601 else if (http_result == HTTPCode_Bad) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response");
602 else if (http_result == HTTPCode_Other) LogMsg("handleLNTPortMappingResponse got unexpected response code");
603 else if (http_result == HTTPCode_404) LNT_ClearState(m);
604 if (http_result != HTTPCode_200 && http_result != HTTPCode_500)
605 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "HTTP Result", "HTTP code: %d", http_result);
606 }
607
608 mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo)
609 {
610 tcpLNTInfo **ptr = &m->tcpInfoUnmapList;
611 while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next;
612 if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); } // If we found it, cut it from our list and free the memory
613 }
614
615 mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err)
616 {
617 mStatus status = mStatus_NoError;
618 tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context;
619 mDNSBool closed = mDNSfalse;
620 long n = 0;
621 long nsent = 0;
622
623 if (tcpInfo == mDNSNULL) { LogInfo("tcpConnectionCallback: no tcpInfo context"); status = mStatus_Invalid; goto exit; }
624
625 // The handlers below expect to be called with the lock held
626 mDNS_Lock(tcpInfo->m);
627
628 if (err) { LogInfo("tcpConnectionCallback: received error"); goto exit; }
629
630 if (ConnectionEstablished) // connection is established - send the message
631 {
632 LogInfo("tcpConnectionCallback: connection established, sending message");
633 nsent = mDNSPlatformWriteTCP(sock, (char *)tcpInfo->Request, tcpInfo->requestLen);
634 if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; }
635 }
636 else
637 {
638 n = mDNSPlatformReadTCP(sock, (char *)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed);
639 LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n);
640
641 if (n < 0) { LogInfo("tcpConnectionCallback - read returned %d", n); status = mStatus_ConnFailed; goto exit; }
642 else if (closed) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; }
643
644 tcpInfo->nread += n;
645 LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread);
646 if (tcpInfo->nread > LNT_MAXBUFSIZE)
647 {
648 LogInfo("result truncated...");
649 tcpInfo->nread = LNT_MAXBUFSIZE;
650 }
651
652 switch (tcpInfo->op)
653 {
654 case LNTDiscoveryOp: handleLNTDeviceDescriptionResponse (tcpInfo); break;
655 case LNTExternalAddrOp: handleLNTGetExternalAddressResponse(tcpInfo); break;
656 case LNTPortMapOp: handleLNTPortMappingResponse (tcpInfo); break;
657 case LNTPortMapDeleteOp: status = mStatus_ConfigChanged; break;
658 default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break;
659 }
660 }
661 exit:
662 if (err || status)
663 {
664 mDNS *m = tcpInfo->m;
665 switch (tcpInfo->op)
666 {
667 case LNTDiscoveryOp: if (m->UPnPSOAPAddressString == mDNSNULL)
668 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP Address", "");
669 if (m->UPnPSOAPURL == mDNSNULL)
670 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP path", "");
671 if (m->UPnPSOAPAddressString && m->UPnPSOAPURL)
672 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "success", "success", "");
673 break;
674 case LNTExternalAddrOp: mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", mDNSIPv4AddressIsZero(m->ExternalAddress) ? "failure" : "success", mDNSIPv4AddressIsZero(m->ExternalAddress) ? "failure" : "success", "");
675 break;
676 case LNTPortMapOp: if (tcpInfo->parentNATInfo)
677 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", (tcpInfo->parentNATInfo->Result) ? "failure" : "success",
678 (tcpInfo->parentNATInfo->Result) ? "failure" : "success", "Result: %d", tcpInfo->parentNATInfo->Result);
679 break;
680 case LNTPortMapDeleteOp: break;
681 default: break;
682 }
683
684 mDNSPlatformTCPCloseConnection(tcpInfo->sock);
685 tcpInfo->sock = mDNSNULL;
686 if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; }
687 if (tcpInfo->Reply ) { mDNSPlatformMemFree(tcpInfo->Reply); tcpInfo->Reply = mDNSNULL; }
688 }
689
690 if (tcpInfo) mDNS_Unlock(tcpInfo->m);
691
692 if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo);
693 }
694
695 mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op)
696 {
697 mStatus err = mStatus_NoError;
698 mDNSIPPort srcport = zeroIPPort;
699
700 if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port))
701 { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); }
702 info->m = m;
703 info->Address = *Addr;
704 info->Port = Port;
705 info->op = op;
706 info->nread = 0;
707 info->replyLen = LNT_MAXBUFSIZE;
708 if (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE); // reuse previously allocated buffer
709 else if ((info->Reply = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr); }
710
711 if (info->sock) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; }
712 info->sock = mDNSPlatformTCPSocket(m, kTCPSocketFlags_Zero, &srcport);
713 if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); }
714 LogInfo("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port));
715 err = mDNSPlatformTCPConnect(info->sock, Addr, Port, 0, tcpConnectionCallback, info);
716
717 if (err == mStatus_ConnPending) err = mStatus_NoError;
718 else if (err == mStatus_ConnEstablished)
719 {
720 mDNS_DropLockBeforeCallback();
721 tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError);
722 mDNS_ReclaimLockAfterCallback();
723 err = mStatus_NoError;
724 }
725 else
726 {
727 // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc.
728 LogInfo("LNT MakeTCPConnection: connection failed");
729 mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above
730 info->sock = mDNSNULL;
731 mDNSPlatformMemFree(info->Reply);
732 info->Reply = mDNSNULL;
733 }
734 return(err);
735 }
736
737 mDNSlocal unsigned int AddSOAPArguments(char *buf, unsigned int maxlen, int numArgs, Property *a)
738 {
739 static const char f1[] = "<%s>%s</%s>";
740 static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>";
741 int i, len = 0;
742 *buf = 0;
743 for (i = 0; i < numArgs; i++)
744 {
745 if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name);
746 else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name, a[i].value, a[i].name);
747 }
748 return(len);
749 }
750
751 mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, char *Action, int numArgs, Property *Arguments, LNTOp_t op)
752 {
753 // SOAP message header format -
754 // - control URL
755 // - action (string)
756 // - router's host/port ("host:port")
757 // - content-length
758 static const char header[] =
759 "POST %s HTTP/1.1\r\n"
760 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
761 "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n"
762 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n"
763 "Host: %s\r\n"
764 "Content-Length: %d\r\n"
765 "Connection: close\r\n"
766 "Pragma: no-cache\r\n"
767 "\r\n"
768 "%s\r\n";
769
770 static const char body1[] =
771 "<?xml version=\"1.0\"?>\r\n"
772 "<SOAP-ENV:Envelope"
773 " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
774 " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
775 "<SOAP-ENV:Body>"
776 "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">";
777
778 static const char body2[] =
779 "</m:%s>"
780 "</SOAP-ENV:Body>"
781 "</SOAP-ENV:Envelope>\r\n";
782
783 mStatus err;
784 char *body = (char *)&m->omsg; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty
785 int bodyLen;
786
787 if (mDNSIPPortIsZero(m->UPnPSOAPPort) || m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL) // if no SOAP URL or address exists get out here
788 { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid; }
789
790 // Create body
791 bodyLen = mDNS_snprintf (body, sizeof(m->omsg), body1, Action, m->UPnPWANPPPConnection ? "PPP" : "IP");
792 bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments);
793 bodyLen += mDNS_snprintf (body + bodyLen, sizeof(m->omsg) - bodyLen, body2, Action);
794
795 // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field
796 if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE);
797 if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; }
798 info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body);
799
800 err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op);
801 if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; }
802 return err;
803 }
804
805 // Build port mapping request with new port (up to max) and send it
806 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n)
807 {
808 char externalPort[6];
809 char internalPort[6];
810 char localIPAddrString[30];
811 char publicPortString[40];
812 Property propArgs[8];
813 mDNSu16 ReqPortNum = RequestedPortNum(n);
814 NATTraversalInfo *n2 = m->NATTraversals;
815
816 // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique.
817 // UPnP gateways will report conflicts if different devices request the same external port, but if two
818 // clients on the same device request the same external port the second one just stomps over the first.
819 // One way this can happen is like this:
820 // 1. Client A binds local port 80
821 // 2. Client A requests external port 80 -> internal port 80
822 // 3. UPnP NAT gateway refuses external port 80 (some other client already has it)
823 // 4. Client A tries again, and successfully gets external port 80 -> internal port 81
824 // 5. Client B on same machine tries to bind local port 80, and fails
825 // 6. Client B tries again, and successfully binds local port 81
826 // 7. Client B now requests external port 81 -> internal port 81
827 // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping
828
829 while (n2)
830 {
831 if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next;
832 else
833 {
834 if (n->tcpInfo.retries < 100)
835 {
836 n->tcpInfo.retries++;
837 ReqPortNum = RequestedPortNum(n); // Pick a new port number
838 n2 = m->NATTraversals; // And re-scan the list looking for conflicts
839 }
840 else
841 {
842 natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0);
843 return mStatus_NoError;
844 }
845 }
846 }
847
848 // create strings to use in the message
849 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", ReqPortNum);
850 mDNS_snprintf(internalPort, sizeof(internalPort), "%u", mDNSVal16(n->IntPort));
851 mDNS_snprintf(publicPortString, sizeof(publicPortString), "iC%u", ReqPortNum);
852 mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u",
853 m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]);
854
855 // build the message
856 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
857 propArgs[0].name = "NewRemoteHost";
858 propArgs[0].type = "string";
859 propArgs[0].value = "";
860 propArgs[1].name = "NewExternalPort";
861 propArgs[1].type = "ui2";
862 propArgs[1].value = externalPort;
863 propArgs[2].name = "NewProtocol";
864 propArgs[2].type = "string";
865 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
866 propArgs[3].name = "NewInternalPort";
867 propArgs[3].type = "ui2";
868 propArgs[3].value = internalPort;
869 propArgs[4].name = "NewInternalClient";
870 propArgs[4].type = "string";
871 propArgs[4].value = localIPAddrString;
872 propArgs[5].name = "NewEnabled";
873 propArgs[5].type = "boolean";
874 propArgs[5].value = "1";
875 propArgs[6].name = "NewPortMappingDescription";
876 propArgs[6].type = "string";
877 propArgs[6].value = publicPortString;
878 propArgs[7].name = "NewLeaseDuration";
879 propArgs[7].type = "ui4";
880 propArgs[7].value = "0";
881
882 LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum);
883 return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp);
884 }
885
886 mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *n)
887 {
888 LogInfo("LNT_MapPort");
889 if (n->tcpInfo.sock) return(mStatus_NoError); // If we already have a connection up don't make another request for the same thing
890 n->tcpInfo.parentNATInfo = n;
891 n->tcpInfo.retries = 0;
892 return SendPortMapRequest(m, n);
893 }
894
895 mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *n)
896 {
897 char externalPort[10];
898 Property propArgs[3];
899 tcpLNTInfo *info;
900 tcpLNTInfo **infoPtr = &m->tcpInfoUnmapList;
901 mStatus err;
902
903 // If no NAT gateway to talk to, no need to do all this work for nothing
904 if (mDNSIPPortIsZero(m->UPnPSOAPPort) || !m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError;
905
906 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort));
907
908 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
909 propArgs[0].name = "NewRemoteHost";
910 propArgs[0].type = "string";
911 propArgs[0].value = "";
912 propArgs[1].name = "NewExternalPort";
913 propArgs[1].type = "ui2";
914 propArgs[1].value = externalPort;
915 propArgs[2].name = "NewProtocol";
916 propArgs[2].type = "string";
917 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
918
919 n->tcpInfo.parentNATInfo = n;
920
921 // clean up previous port mapping requests and allocations
922 if (n->tcpInfo.sock) LogInfo("LNT_UnmapPort: closing previous open connection");
923 if (n->tcpInfo.sock ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock = mDNSNULL; }
924 if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request); n->tcpInfo.Request = mDNSNULL; }
925 if (n->tcpInfo.Reply ) { mDNSPlatformMemFree(n->tcpInfo.Reply); n->tcpInfo.Reply = mDNSNULL; }
926
927 // make a copy of the tcpInfo that we can clean up later (the one passed in will be destroyed by the client as soon as this returns)
928 if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL)
929 { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); }
930 *info = n->tcpInfo;
931
932 while (*infoPtr) infoPtr = &(*infoPtr)->next; // find the end of the list
933 *infoPtr = info; // append
934
935 err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp);
936 if (err) DisposeInfoFromUnmapList(m, info);
937 return err;
938 }
939
940 mDNSexport mStatus LNT_GetExternalAddress(mDNS *m)
941 {
942 return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp);
943 }
944
945 mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info)
946 {
947 // Device description format -
948 // - device description URL
949 // - host/port
950 static const char szSSDPMsgDescribeDeviceFMT[] =
951 "GET %s HTTP/1.1\r\n"
952 "Accept: text/xml, application/xml\r\n"
953 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n"
954 "Host: %s\r\n"
955 "Connection: close\r\n"
956 "\r\n";
957
958 if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return mStatus_NoError; // already have the info we need
959
960 if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); }
961
962 // build message
963 if (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer
964 else if ((info->Request = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); }
965 info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString);
966 LogInfo("Describe Device: [%s]", info->Request);
967 return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp);
968 }
969
970 // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response
971 // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and
972 // URL info we need.
973 mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, mDNSu8 *data, mDNSu16 len)
974 {
975 char *ptr = (char *)data;
976 char *end = (char *)data + len;
977 char *stop = ptr;
978
979 if (!mDNSIPPortIsZero(m->UPnPRouterPort)) return; // already have the info we need
980
981 // The formatting of the HTTP header is not always the same when it comes to the placement of
982 // the service and location strings, so we just look for each of them from the beginning for every response
983
984 // figure out if this is a message from a service we care about
985 while (ptr && ptr != end)
986 {
987 if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break;
988 ptr++;
989 }
990 if (ptr == end)
991 {
992 ptr = (char *)data;
993 while (ptr && ptr != end)
994 {
995 if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0)) break;
996 ptr++;
997 }
998 }
999 if (ptr == mDNSNULL || ptr == end) return; // not a message we care about
1000
1001 // find "Location:", starting from the beginning
1002 ptr = (char *)data;
1003 while (ptr && ptr != end)
1004 {
1005 if ((*ptr & 0xDF) == 'L' && (strncasecmp(ptr, "Location:", 9) == 0)) break; // find the first 'L'; is this Location? if not, keep looking
1006 ptr++;
1007 }
1008 if (ptr == mDNSNULL || ptr == end)
1009 {
1010 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Location", "");
1011 return; // not a message we care about
1012 }
1013 ptr += 9; //Skip over 'Location:'
1014 while (*ptr == ' ' && ptr < end) ptr++; // skip over spaces
1015 if (ptr >= end) return;
1016
1017 // find the end of the line
1018 for (stop = ptr; stop != end; stop++) { if (*stop == '\r') { end = stop; break; } }
1019
1020 // fill in default port
1021 m->UPnPRouterPort = mDNSOpaque16fromIntVal(80);
1022
1023 // free string pointers and set to NULL
1024 if (m->UPnPRouterAddressString != mDNSNULL)
1025 {
1026 mDNSPlatformMemFree(m->UPnPRouterAddressString);
1027 m->UPnPRouterAddressString = mDNSNULL;
1028 }
1029 if (m->UPnPRouterURL != mDNSNULL)
1030 {
1031 mDNSPlatformMemFree(m->UPnPRouterURL);
1032 m->UPnPRouterURL = mDNSNULL;
1033 }
1034
1035 // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc"
1036 if (ParseHttpUrl(ptr, end, &m->UPnPRouterAddressString, &m->UPnPRouterPort, &m->UPnPRouterURL) != mStatus_NoError)
1037 {
1038 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Parse URL", "");
1039 return;
1040 }
1041
1042 m->UPnPInterfaceID = InterfaceID;
1043
1044 if (m->UPnPRouterAddressString == mDNSNULL)
1045 {
1046 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router address", "");
1047 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL");
1048 }
1049 else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m->UPnPRouterAddressString);
1050
1051 if (m->UPnPRouterURL == mDNSNULL)
1052 {
1053 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router path", "");
1054 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL");
1055 }
1056 else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m->UPnPRouterURL);
1057
1058 LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m->UPnPRouterPort));
1059 LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m->UPnPInterfaceID);
1060
1061 // Don't need the SSDP socket anymore
1062 if (m->SSDPSocket) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
1063
1064 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "success", "success", "");
1065 // now send message to get the device description
1066 GetDeviceDescription(m, &m->tcpDeviceInfo);
1067 }
1068
1069 mDNSexport void LNT_SendDiscoveryMsg(mDNS *m)
1070 {
1071 static const char msg[] =
1072 "M-SEARCH * HTTP/1.1\r\n"
1073 "Host:239.255.255.250:1900\r\n"
1074 "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n"
1075 "Man:\"ssdp:discover\"\r\n"
1076 "MX:3\r\n\r\n";
1077 static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } };
1078
1079 mDNSu8* buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
1080 unsigned int bufLen;
1081
1082 if (!mDNSIPPortIsZero(m->UPnPRouterPort))
1083 {
1084 if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
1085 if (mDNSIPPortIsZero(m->UPnPSOAPPort) && !m->tcpDeviceInfo.sock) GetDeviceDescription(m, &m->tcpDeviceInfo);
1086 return;
1087 }
1088
1089 // Always query for WANIPConnection in the first SSDP packet
1090 if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse;
1091
1092 // Create message
1093 bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP");
1094
1095 debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExternalAddress);
1096
1097 if (!mDNSIPv4AddressIsZero(m->Router.ip.v4))
1098 {
1099 if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(m, zeroIPPort); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); }
1100 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router, SSDPPort);
1101 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort);
1102 }
1103
1104 m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection;
1105 }
1106
1107 mDNSexport void LNT_ClearState(mDNS *const m)
1108 {
1109 if (m->tcpAddrInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpAddrInfo.sock); m->tcpAddrInfo.sock = mDNSNULL; }
1110 if (m->tcpDeviceInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpDeviceInfo.sock); m->tcpDeviceInfo.sock = mDNSNULL; }
1111 m->UPnPSOAPPort = m->UPnPRouterPort = zeroIPPort; // Reset UPnP ports
1112 }
1113
1114 #endif /* _LEGACY_NAT_TRAVERSAL_ */