]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/LegacyNATTraversal.c
mDNSResponder-176.3.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.48.2.1 2008/09/30 18:03:03 mcguire
21 <rdar://problem/6129039> BTMM: Add support for WANPPPConnection
22
23 Revision 1.48 2008/07/24 20:23:04 cheshire
24 <rdar://problem/3988320> Should use randomized source ports and transaction IDs to avoid DNS cache poisoning
25
26 Revision 1.47 2008/07/18 21:37:46 mcguire
27 <rdar://problem/5736845> BTMM: alternate SSDP queries between multicast & unicast
28
29 Revision 1.46 2008/05/13 01:51:12 mcguire
30 <rdar://problem/5839161> UPnP compatibility workaround for Netgear WGT624
31
32 Revision 1.45 2007/12/06 00:22:27 mcguire
33 <rdar://problem/5604567> BTMM: Doesn't work with Linksys WAG300N 1.01.06 (sending from 1026/udp)
34
35 Revision 1.44 2007/11/02 20:45:40 cheshire
36 Don't log "connection failed" in customer builds
37
38 Revision 1.43 2007/10/18 20:09:47 cheshire
39 <rdar://problem/5545930> BTMM: Back to My Mac not working with D-Link DGL-4100 NAT gateway
40
41 Revision 1.42 2007/10/16 17:37:18 cheshire
42 <rdar://problem/3557903> Performance: Core code will not work on platforms with small stacks
43 Cut SendSOAPMsgControlAction stack from 2144 to 96 bytes
44
45 Revision 1.41 2007/10/15 23:02:00 cheshire
46 Off-by-one error: Incorrect trailing zero byte on the end of the SSDP Discovery message
47
48 Revision 1.40 2007/09/20 21:41:49 cheshire
49 <rdar://problem/5495568> Legacy NAT Traversal - unmap request failed with error -65549
50
51 Revision 1.39 2007/09/20 20:41:40 cheshire
52 Reordered functions in file, in preparation for following fix
53
54 Revision 1.38 2007/09/18 21:42:30 cheshire
55 To reduce programming mistakes, renamed ExtPort to RequestedPort
56
57 Revision 1.37 2007/09/14 21:26:09 cheshire
58 <rdar://problem/5482627> BTMM: Need to manually avoid port conflicts when using UPnP gateways
59
60 Revision 1.36 2007/09/14 01:15:50 cheshire
61 Minor fixes for problems discovered in pre-submission testing
62
63 Revision 1.35 2007/09/13 00:16:42 cheshire
64 <rdar://problem/5468706> Miscellaneous NAT Traversal improvements
65
66 Revision 1.34 2007/09/12 23:03:08 cheshire
67 <rdar://problem/5476978> DNSServiceNATPortMappingCreate callback not giving correct interface index
68
69 Revision 1.33 2007/09/12 19:22:20 cheshire
70 Variable renaming in preparation for upcoming fixes e.g. priv/pub renamed to intport/extport
71 Made NAT Traversal packet handlers take typed data instead of anonymous "mDNSu8 *" byte pointers
72
73 Revision 1.32 2007/09/11 19:19:16 cheshire
74 Correct capitalization of "uPNP" to "UPnP"
75
76 Revision 1.31 2007/09/10 22:14:16 cheshire
77 When constructing fake NATAddrReply or NATPortMapReply packet, need to calculate
78 plausible upseconds value or core logic will think NAT engine has been rebooted
79
80 Revision 1.30 2007/09/05 20:46:17 cheshire
81 Tidied up alignment of code layout
82
83 Revision 1.29 2007/08/03 20:18:01 vazquez
84 <rdar://problem/5382177> LegacyNATTraversal: reading out of bounds can lead to DoS
85
86 Revision 1.28 2007/07/31 02:28:36 vazquez
87 <rdar://problem/3734269> NAT-PMP: Detect public IP address changes and base station reboot
88
89 Revision 1.27 2007/07/30 23:17:03 vazquez
90 Since lease times are meaningless in UPnP, return NATMAP_DEFAULT_LEASE in UPnP port mapping reply
91
92 Revision 1.26 2007/07/27 22:50:08 vazquez
93 Allocate memory for UPnP request and reply buffers instead of using arrays
94
95 Revision 1.25 2007/07/27 20:33:44 vazquez
96 Make sure we clean up previous port mapping requests before starting an unmap
97
98 Revision 1.24 2007/07/27 00:57:48 vazquez
99 If a tcp connection is already established for doing a port mapping, don't start it again
100
101 Revision 1.23 2007/07/26 21:19:26 vazquez
102 Retry port mapping with incremented port number (up to max) in order to handle
103 port mapping conflicts on UPnP gateways
104
105 Revision 1.22 2007/07/25 21:41:00 vazquez
106 Make sure we clean up opened sockets when there are network transitions and when changing
107 port mappings
108
109 Revision 1.21 2007/07/25 03:05:03 vazquez
110 Fixes for:
111 <rdar://problem/5338913> LegacyNATTraversal: UPnP heap overflow
112 <rdar://problem/5338933> LegacyNATTraversal: UPnP stack buffer overflow
113 and a myriad of other security problems
114
115 Revision 1.20 2007/07/16 20:15:10 vazquez
116 <rdar://problem/3867231> LegacyNATTraversal: Need complete rewrite
117
118 Revision 1.19 2007/06/21 16:37:43 jgraessley
119 Bug #: 5280520
120 Reviewed by: Stuart Cheshire
121 Additional changes to get this compiling on the embedded platform.
122
123 Revision 1.18 2007/05/09 01:43:32 cheshire
124 <rdar://problem/5187028> Change sprintf and strcpy to their safer snprintf and strlcpy equivalents
125
126 Revision 1.17 2007/02/27 02:48:25 cheshire
127 Parameter to LNT_GetPublicIP function is IPv4 address, not anonymous "mDNSOpaque32" object
128
129 Revision 1.16 2006/08/14 23:24:39 cheshire
130 Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
131
132 Revision 1.15 2006/07/05 23:30:57 cheshire
133 Rename LegacyNATInit() -> LNT_Init()
134
135 Revision 1.14 2005/12/08 03:00:33 cheshire
136 <rdar://problem/4349971> Byte order bugs in Legacy NAT traversal code
137
138 Revision 1.13 2005/09/07 18:23:05 ksekar
139 <rdar://problem/4151514> Off-by-one overflow in LegacyNATTraversal
140
141 Revision 1.12 2005/07/22 21:36:16 ksekar
142 Fix GCC 4.0/Intel compiler warnings
143
144 Revision 1.11 2004/12/03 03:34:20 ksekar
145 <rdar://problem/3882674> LegacyNATTraversal.c leaks threads
146
147 Revision 1.10 2004/12/01 02:43:49 cheshire
148 Update copyright message
149
150 Revision 1.9 2004/10/27 02:25:05 cheshire
151 <rdar://problem/3816029> Random memory smashing bug
152
153 Revision 1.8 2004/10/27 02:17:21 cheshire
154 Turn off "safe_close: ERROR" error messages -- there are too many of them
155
156 Revision 1.7 2004/10/26 21:15:40 cheshire
157 <rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0
158 Additional fixes: Code should set fds to -1 after closing sockets.
159
160 Revision 1.6 2004/10/26 20:59:20 cheshire
161 <rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0
162
163 Revision 1.5 2004/10/26 01:01:35 cheshire
164 Use "#if 0" instead of commenting out code
165
166 Revision 1.4 2004/10/10 06:51:36 cheshire
167 Declared some strings "const" as appropriate
168
169 Revision 1.3 2004/09/21 23:40:12 ksekar
170 <rdar://problem/3810349> mDNSResponder to return errors on NAT traversal failure
171
172 Revision 1.2 2004/09/17 01:08:52 cheshire
173 Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h
174 The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces
175 declared in that file are ONLY appropriate to single-address-space embedded applications.
176 For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used.
177
178 Revision 1.1 2004/08/18 17:35:41 ksekar
179 <rdar://problem/3651443>: Feature #9586: Need support for Legacy NAT gateways
180 */
181
182 #ifdef _LEGACY_NAT_TRAVERSAL_
183
184 #include "stdlib.h" // For strtol()
185 #include "string.h" // For strlcpy(), For strncpy(), strncasecmp()
186 #include <arpa/inet.h> // For inet_pton()
187
188 #include "mDNSEmbeddedAPI.h"
189 #include "uDNS.h" // For natTraversalHandleAddressReply() etc.
190
191 // used to format SOAP port mapping arguments
192 typedef struct Property_struct
193 {
194 char *name;
195 char *type;
196 char *value;
197 } Property;
198
199 // All of the text parsing in this file is intentionally transparent so that we know exactly
200 // what's being done to the text, with an eye towards preventing security problems.
201
202 // This is an evolving list of useful acronyms to know. Please add to it at will.
203 // ST Service Type
204 // NT Notification Type
205 // USN Unique Service Name
206 // UDN Unique Device Name
207 // UUID Universally Unique Identifier
208 // URN/urn Universal Resource Name
209
210 // Forward declaration because of circular reference:
211 // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse
212 // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again
213 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n);
214
215 #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (n)->tcpInfo.retries)
216
217 // This function parses the xml body of the device description response from the router. Basically, we look to make sure this is a response
218 // 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
219 mDNSlocal void handleLNTDeviceDescriptionResponse(tcpLNTInfo *tcpInfo)
220 {
221 mDNS *m = tcpInfo->m;
222 char *ptr = (char *)tcpInfo->Reply;
223 char *end = (char *)tcpInfo->Reply + tcpInfo->nread;
224 char *stop = mDNSNULL;
225
226 // Always reset our flag to use WANIPConnection. We'll use WANPPPConnection if we find it and don't find WANIPConnection.
227 m->UPnPWANPPPConnection = mDNSfalse;
228
229 // find either service we care about
230 while (ptr && ptr != end)
231 {
232 if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break;
233 ptr++;
234 }
235 if (ptr == end)
236 {
237 ptr = (char *)tcpInfo->Reply;
238 while (ptr && ptr != end)
239 {
240 if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0))
241 {
242 m->UPnPWANPPPConnection = mDNStrue;
243 break;
244 }
245 ptr++;
246 }
247 }
248 if (ptr == mDNSNULL || ptr == end) { LogOperation("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; }
249
250 // find "controlURL", starting from where we left off
251 while (ptr && ptr != end)
252 {
253 if (*ptr == 'c' && (strncasecmp(ptr, "controlURL", 10) == 0)) break; // find the first 'c'; is this controlURL? if not, keep looking
254 ptr++;
255 }
256 if (ptr == mDNSNULL || ptr == end) { LogOperation("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; }
257 ptr += 11; // skip over "controlURL>"
258 if (ptr >= end) { LogOperation("handleLNTDeviceDescriptionResponse: past end of buffer and no body!"); return; } // check ptr again in case we skipped over the end of the buffer
259
260 // find the end of the controlURL element
261 for (stop = ptr; stop != end; stop++) { if (*stop == '<') { end = stop; break; } }
262
263 // fill in default port
264 m->UPnPSOAPPort = m->UPnPRouterPort;
265
266 // is there an address string "http://"?
267 if (strncasecmp(ptr, "http://", 7) == 0)
268 {
269 int i;
270 char *addrPtr = mDNSNULL;
271
272 ptr += 7; //skip over "http://"
273 if (ptr >= end) { LogOperation("handleLNTDeviceDescriptionResponse: past end of buffer and no URL!"); return; }
274 addrPtr = ptr;
275 for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break; // first find the beginning of the URL and count the chars
276 if (addrPtr == mDNSNULL || addrPtr == end) { LogOperation("handleLNTDeviceDescriptionResponse: didn't find SOAP address string"); return; }
277
278 // allocate the buffer (len i+1 so we have space to terminate the string)
279 if (m->UPnPSOAPAddressString != mDNSNULL) mDNSPlatformMemFree(m->UPnPSOAPAddressString);
280 if ((m->UPnPSOAPAddressString = (mDNSu8 *) mDNSPlatformMemAllocate(i+1)) == mDNSNULL) { LogMsg("can't allocate SOAP address string"); return; }
281
282 strncpy((char *)m->UPnPSOAPAddressString, ptr, i); // copy the address string
283 m->UPnPSOAPAddressString[i] = '\0'; // terminate the string
284
285 stop = ptr; // remember where to stop (just after "http://")
286 ptr = addrPtr; // move ptr past the rest of what we just processed
287
288 // find the port number in the string
289 for (addrPtr--;addrPtr>stop;addrPtr--)
290 {
291 if (*addrPtr == ':')
292 {
293 int port;
294 addrPtr++; // skip over ':'
295 port = (int)strtol(addrPtr, mDNSNULL, 10);
296 m->UPnPSOAPPort = mDNSOpaque16fromIntVal(port); // store it properly converted
297 break;
298 }
299 }
300 }
301
302 if (m->UPnPSOAPAddressString == mDNSNULL) m->UPnPSOAPAddressString = m->UPnPRouterAddressString; // just copy the pointer, don't allocate more memory
303 LogOperation("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m->UPnPSOAPAddressString);
304
305 // ptr should now point to the first character we haven't yet processed
306 if (ptr != end)
307 {
308 // allocate the buffer
309 if (m->UPnPSOAPURL != mDNSNULL) mDNSPlatformMemFree(m->UPnPSOAPURL);
310 if ((m->UPnPSOAPURL = (mDNSu8 *)mDNSPlatformMemAllocate(end - ptr + 1)) == mDNSNULL) { LogMsg("can't mDNSPlatformMemAllocate SOAP URL"); return; }
311
312 // now copy
313 strncpy((char *)m->UPnPSOAPURL, ptr, end - ptr); // this URL looks something like "/uuid:0013-108c-4b3f0000f3dc"
314 m->UPnPSOAPURL[end - ptr] = '\0'; // terminate the string
315 }
316
317 // if we get to the end and haven't found the URL fill in the defaults
318 if (m->UPnPSOAPURL == mDNSNULL) m->UPnPSOAPURL = m->UPnPRouterURL; // just copy the pointer, don't allocate more memory
319
320 LogOperation("handleLNTDeviceDescriptionResponse: SOAP URL [%s] port %d", m->UPnPSOAPURL, mDNSVal16(m->UPnPSOAPPort));
321 }
322
323 mDNSlocal void handleLNTGetExternalAddressResponse(tcpLNTInfo *tcpInfo)
324 {
325 mDNS *m = tcpInfo->m;
326 mDNSu16 err = NATErr_None;
327 mDNSv4Addr ExtAddr;
328 char *ptr = (char *)tcpInfo->Reply;
329 char *end = (char *)tcpInfo->Reply + tcpInfo->nread;
330 char *addrend;
331 static char tagname[20] = "NewExternalIPAddress"; // Array NOT including a terminating nul
332
333 // LogOperation("handleLNTGetExternalAddressResponse: %s", ptr);
334
335 while (ptr < end && strncasecmp(ptr, tagname, sizeof(tagname))) ptr++;
336 ptr += sizeof(tagname); // Skip over "NewExternalIPAddress"
337 while (ptr < end && *ptr != '>') ptr++;
338 ptr += 1; // Skip over ">"
339 // Find the end of the address and terminate the string so inet_pton() can convert it
340 addrend = ptr;
341 while (addrend < end && (mdnsIsDigit(*addrend) || *addrend == '.')) addrend++;
342 if (addrend >= end) return;
343 *addrend = 0;
344
345 if (inet_pton(AF_INET, ptr, &ExtAddr) <= 0)
346 { LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr); err = NATErr_NetFail; }
347 if (!err) LogOperation("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr);
348
349 natTraversalHandleAddressReply(m, err, ExtAddr);
350 }
351
352 mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo)
353 {
354 mDNS *m = tcpInfo->m;
355 mDNSIPPort extport = zeroIPPort;
356 char *ptr = (char *)tcpInfo->Reply;
357 char *end = (char *)tcpInfo->Reply + tcpInfo->nread;
358 NATTraversalInfo *natInfo;
359
360 for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break; }
361
362 if (!natInfo) { LogOperation("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; }
363
364 // start from the beginning of the HTTP header; find "200 OK" status message; if the first characters after the
365 // space are not "200" then this is an error message or invalid in some other way
366 // if the error is "500" this is an internal server error
367 while (ptr && ptr != end)
368 {
369 if (*ptr == ' ')
370 {
371 ptr++;
372 if (ptr == end) { LogOperation("handleLNTPortMappingResponse: past end of buffer!"); return; }
373 if (strncasecmp(ptr, "200", 3) == 0) break;
374 else if (strncasecmp(ptr, "500", 3) == 0)
375 {
376 // now check to see if this was a port mapping conflict
377 while (ptr && ptr != end)
378 {
379 if ((*ptr == 'c' || *ptr == 'C') && strncasecmp(ptr, "Conflict", 8) == 0)
380 {
381 if (tcpInfo->retries < 100)
382 { tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo); }
383 else
384 {
385 LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort));
386 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Refused, zeroIPPort, 0);
387 }
388 return;
389 }
390 ptr++;
391 }
392 break; // out of HTTP status search
393 }
394 }
395 ptr++;
396 }
397 if (ptr == mDNSNULL || ptr == end) return;
398
399 LogOperation("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)",
400 mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries);
401
402 // Make sure to compute extport *before* we zero tcpInfo->retries
403 extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo));
404 tcpInfo->retries = 0;
405 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE);
406 }
407
408 mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo)
409 {
410 tcpLNTInfo **ptr = &m->tcpInfoUnmapList;
411 while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next;
412 if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); } // If we found it, cut it from our list and free the memory
413 }
414
415 mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err)
416 {
417 mStatus status = mStatus_NoError;
418 tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context;
419 mDNSBool closed = mDNSfalse;
420 long n = 0;
421 long nsent = 0;
422
423 if (tcpInfo == mDNSNULL) { LogOperation("tcpConnectionCallback: no tcpInfo context"); status = mStatus_Invalid; goto exit; }
424
425 // The handlers below expect to be called with the lock held
426 mDNS_Lock(tcpInfo->m);
427
428 if (err) { LogOperation("tcpConnectionCallback: received error"); goto exit; }
429
430 if (ConnectionEstablished) // connection is established - send the message
431 {
432 LogOperation("tcpConnectionCallback: connection established, sending message");
433 nsent = mDNSPlatformWriteTCP(sock, (char *)tcpInfo->Request, tcpInfo->requestLen);
434 if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; }
435 }
436 else
437 {
438 n = mDNSPlatformReadTCP(sock, (char *)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed);
439 LogOperation("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n);
440
441 if (n < 0) { LogOperation("tcpConnectionCallback - read returned %d", n); status = mStatus_ConnFailed; goto exit; }
442 else if (closed) { LogOperation("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; }
443
444 tcpInfo->nread += n;
445 LogOperation("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread);
446 if (tcpInfo->nread > LNT_MAXBUFSIZE)
447 {
448 LogOperation("result truncated...");
449 tcpInfo->nread = LNT_MAXBUFSIZE;
450 }
451
452 switch (tcpInfo->op)
453 {
454 case LNTDiscoveryOp: handleLNTDeviceDescriptionResponse (tcpInfo); break;
455 case LNTExternalAddrOp: handleLNTGetExternalAddressResponse(tcpInfo); break;
456 case LNTPortMapOp: handleLNTPortMappingResponse (tcpInfo); break;
457 case LNTPortMapDeleteOp: status = mStatus_ConfigChanged; break;
458 default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break;
459 }
460 }
461 exit:
462 if (err || status)
463 {
464 mDNSPlatformTCPCloseConnection(tcpInfo->sock);
465 tcpInfo->sock = mDNSNULL;
466 if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; }
467 if (tcpInfo->Reply ) { mDNSPlatformMemFree(tcpInfo->Reply); tcpInfo->Reply = mDNSNULL; }
468 }
469
470 if (tcpInfo) mDNS_Unlock(tcpInfo->m);
471
472 if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo);
473 }
474
475 mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op)
476 {
477 mStatus err = mStatus_NoError;
478 mDNSIPPort srcport = zeroIPPort;
479
480 if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port))
481 { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); }
482 info->m = m;
483 info->Address = *Addr;
484 info->Port = Port;
485 info->op = op;
486 info->nread = 0;
487 info->replyLen = LNT_MAXBUFSIZE;
488 if (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE); // reuse previously allocated buffer
489 else if ((info->Reply = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogOperation("can't allocate reply buffer"); return (mStatus_NoMemoryErr); }
490
491 if (info->sock) { LogOperation("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; }
492 info->sock = mDNSPlatformTCPSocket(m, kTCPSocketFlags_Zero, &srcport);
493 if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); }
494 LogOperation("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port));
495 err = mDNSPlatformTCPConnect(info->sock, Addr, Port, 0, tcpConnectionCallback, info);
496
497 if (err == mStatus_ConnPending) err = mStatus_NoError;
498 else if (err == mStatus_ConnEstablished)
499 {
500 mDNS_DropLockBeforeCallback();
501 tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError);
502 mDNS_ReclaimLockAfterCallback();
503 err = mStatus_NoError;
504 }
505 else
506 {
507 // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc.
508 LogOperation("LNT MakeTCPConnection: connection failed");
509 mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above
510 info->sock = mDNSNULL;
511 mDNSPlatformMemFree(info->Reply);
512 info->Reply = mDNSNULL;
513 }
514 return(err);
515 }
516
517 mDNSlocal unsigned int AddSOAPArguments(char *buf, unsigned int maxlen, int numArgs, Property *a)
518 {
519 static const char f1[] = "<%s>%s</%s>";
520 static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>";
521 int i, len = 0;
522 *buf = 0;
523 for (i = 0; i < numArgs; i++)
524 {
525 if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name);
526 else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name, a[i].value, a[i].name);
527 }
528 return(len);
529 }
530
531 mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, char *Action, int numArgs, Property *Arguments, LNTOp_t op)
532 {
533 // SOAP message header format -
534 // - control URL
535 // - action (string)
536 // - router's host/port ("host:port")
537 // - content-length
538 static const char header[] =
539 "POST %s HTTP/1.1\r\n"
540 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
541 "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n"
542 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n"
543 "Host: %s\r\n"
544 "Content-Length: %d\r\n"
545 "Connection: close\r\n"
546 "Pragma: no-cache\r\n"
547 "\r\n"
548 "%s\r\n";
549
550 static const char body1[] =
551 "<?xml version=\"1.0\"?>\r\n"
552 "<SOAP-ENV:Envelope"
553 " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
554 " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
555 "<SOAP-ENV:Body>"
556 "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">";
557
558 static const char body2[] =
559 "</m:%s>"
560 "</SOAP-ENV:Body>"
561 "</SOAP-ENV:Envelope>\r\n";
562
563 mStatus err;
564 char *body = (char *)&m->omsg; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty
565 int bodyLen;
566
567 if (m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL) // if no SOAP URL or address exists get out here
568 { LogOperation("SendSOAPMsgControlAction: no SOAP URL or address string"); return mStatus_Invalid; }
569
570 // Create body
571 bodyLen = mDNS_snprintf (body, sizeof(m->omsg), body1, Action, m->UPnPWANPPPConnection ? "PPP" : "IP");
572 bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments);
573 bodyLen += mDNS_snprintf (body + bodyLen, sizeof(m->omsg) - bodyLen, body2, Action);
574
575 // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field
576 if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE);
577 if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; }
578 info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body);
579
580 err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op);
581 if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; }
582 return err;
583 }
584
585 // Build port mapping request with new port (up to max) and send it
586 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n)
587 {
588 char externalPort[6];
589 char internalPort[6];
590 char localIPAddrString[30];
591 char publicPortString[40];
592 Property propArgs[8];
593 mDNSu16 ReqPortNum = RequestedPortNum(n);
594 NATTraversalInfo *n2 = m->NATTraversals;
595
596 // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique.
597 // UPnP gateways will report conflicts if different devices request the same external port, but if two
598 // clients on the same device request the same external port the second one just stomps over the first.
599 // One way this can happen is like this:
600 // 1. Client A binds local port 80
601 // 2. Client A requests external port 80 -> internal port 80
602 // 3. UPnP NAT gateway refuses external port 80 (some other client already has it)
603 // 4. Client A tries again, and successfully gets external port 80 -> internal port 81
604 // 5. Client B on same machine tries to bind local port 80, and fails
605 // 6. Client B tries again, and successfully binds local port 81
606 // 7. Client B now requests external port 81 -> internal port 81
607 // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping
608
609 while (n2)
610 {
611 if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next;
612 else
613 {
614 if (n->tcpInfo.retries < 100)
615 {
616 n->tcpInfo.retries++;
617 ReqPortNum = RequestedPortNum(n); // Pick a new port number
618 n2 = m->NATTraversals; // And re-scan the list looking for conflicts
619 }
620 else
621 {
622 natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Refused, zeroIPPort, 0);
623 return mStatus_NoError;
624 }
625 }
626 }
627
628 // create strings to use in the message
629 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", ReqPortNum);
630 mDNS_snprintf(internalPort, sizeof(internalPort), "%u", mDNSVal16(n->IntPort));
631 mDNS_snprintf(publicPortString, sizeof(publicPortString), "iC%u", ReqPortNum);
632 mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u",
633 m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]);
634
635 // build the message
636 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
637 propArgs[0].name = "NewRemoteHost";
638 propArgs[0].type = "string";
639 propArgs[0].value = "";
640 propArgs[1].name = "NewExternalPort";
641 propArgs[1].type = "ui2";
642 propArgs[1].value = externalPort;
643 propArgs[2].name = "NewProtocol";
644 propArgs[2].type = "string";
645 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
646 propArgs[3].name = "NewInternalPort";
647 propArgs[3].type = "ui2";
648 propArgs[3].value = internalPort;
649 propArgs[4].name = "NewInternalClient";
650 propArgs[4].type = "string";
651 propArgs[4].value = localIPAddrString;
652 propArgs[5].name = "NewEnabled";
653 propArgs[5].type = "boolean";
654 propArgs[5].value = "1";
655 propArgs[6].name = "NewPortMappingDescription";
656 propArgs[6].type = "string";
657 propArgs[6].value = publicPortString;
658 propArgs[7].name = "NewLeaseDuration";
659 propArgs[7].type = "ui4";
660 propArgs[7].value = "0";
661
662 LogOperation("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum);
663 return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp);
664 }
665
666 mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *n)
667 {
668 LogOperation("LNT_MapPort");
669 if (n->tcpInfo.sock) return(mStatus_NoError); // If we already have a connection up don't make another request for the same thing
670 n->tcpInfo.parentNATInfo = n;
671 n->tcpInfo.retries = 0;
672 return SendPortMapRequest(m, n);
673 }
674
675 mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *n)
676 {
677 char externalPort[10];
678 Property propArgs[3];
679 tcpLNTInfo *info;
680 tcpLNTInfo **infoPtr = &m->tcpInfoUnmapList;
681 mStatus err;
682
683 // If no NAT gateway to talk to, no need to do all this work for nothing
684 if (!m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError;
685
686 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort));
687
688 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
689 propArgs[0].name = "NewRemoteHost";
690 propArgs[0].type = "string";
691 propArgs[0].value = "";
692 propArgs[1].name = "NewExternalPort";
693 propArgs[1].type = "ui2";
694 propArgs[1].value = externalPort;
695 propArgs[2].name = "NewProtocol";
696 propArgs[2].type = "string";
697 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
698
699 n->tcpInfo.parentNATInfo = n;
700
701 // clean up previous port mapping requests and allocations
702 if (n->tcpInfo.sock) LogOperation("LNT_UnmapPort: closing previous open connection");
703 if (n->tcpInfo.sock ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock = mDNSNULL; }
704 if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request); n->tcpInfo.Request = mDNSNULL; }
705 if (n->tcpInfo.Reply ) { mDNSPlatformMemFree(n->tcpInfo.Reply); n->tcpInfo.Reply = mDNSNULL; }
706
707 // 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)
708 if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL)
709 { LogOperation("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); }
710 *info = n->tcpInfo;
711
712 while (*infoPtr) infoPtr = &(*infoPtr)->next; // find the end of the list
713 *infoPtr = info; // append
714
715 err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp);
716 if (err) DisposeInfoFromUnmapList(m, info);
717 return err;
718 }
719
720 mDNSexport mStatus LNT_GetExternalAddress(mDNS *m)
721 {
722 return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp);
723 }
724
725 mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info)
726 {
727 // Device description format -
728 // - device description URL
729 // - host/port
730 static const char szSSDPMsgDescribeDeviceFMT[] =
731 "GET %s HTTP/1.1\r\n"
732 "Accept: text/xml, application/xml\r\n"
733 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n"
734 "Host: %s\r\n"
735 "Connection: close\r\n"
736 "\r\n";
737
738 if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogOperation("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); }
739
740 // build message
741 if (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer
742 else if ((info->Request = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogOperation("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); }
743 info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString);
744 LogOperation("Describe Device: [%s]", info->Request);
745 return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp);
746 }
747
748 // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response
749 // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and
750 // URL info we need.
751 mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, mDNSu8 *data, mDNSu16 len)
752 {
753 char *ptr = (char *)data;
754 char *end = (char *)data + len;
755
756 // The formatting of the HTTP header is not always the same when it comes to the placement of
757 // the service and location strings, so we just look for each of them from the beginning for every response
758
759 // figure out if this is a message from a service we care about
760 while (ptr && ptr != end)
761 {
762 if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break;
763 ptr++;
764 }
765 if (ptr == end)
766 {
767 ptr = (char *)data;
768 while (ptr && ptr != end)
769 {
770 if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0)) break;
771 ptr++;
772 }
773 }
774 if (ptr == mDNSNULL || ptr == end) return; // not a message we care about
775
776 // find "Location:", starting from the beginning
777 ptr = (char *)data;
778 while (ptr && ptr != end)
779 {
780 if (*ptr == 'L' && (strncasecmp(ptr, "Location", 8) == 0)) break; // find the first 'L'; is this Location? if not, keep looking
781 ptr++;
782 }
783 if (ptr == mDNSNULL || ptr == end) return; // not a message we care about
784
785 // find "http://", starting from where we left off
786 while (ptr && ptr != end)
787 {
788 if (*ptr == 'h' && (strncasecmp(ptr, "http://", 7) == 0)) // find the first 'h'; is this a URL? if not, keep looking
789 {
790 int i;
791 char *addrPtr = mDNSNULL;
792
793 ptr += 7; //skip over "http://"
794 if (ptr >= end) { LogOperation("LNT_ConfigureRouterInfo: past end of buffer and no URL!"); return; }
795 addrPtr = ptr;
796 for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break; // first find the beginning of the URL and count the chars
797 if (addrPtr == mDNSNULL || addrPtr == end) return; // not a valid message
798
799 // allocate the buffer (len i+1 so we have space to terminate the string)
800 if (m->UPnPRouterAddressString != mDNSNULL) mDNSPlatformMemFree(m->UPnPRouterAddressString);
801 if ((m->UPnPRouterAddressString = (mDNSu8 *) mDNSPlatformMemAllocate(i+1)) == mDNSNULL) { LogMsg("can't mDNSPlatformMemAllocate router address string"); return; }
802
803 strncpy((char *)m->UPnPRouterAddressString, ptr, i); // copy the address string
804 m->UPnPRouterAddressString[i] = '\0'; // terminate the string
805 LogOperation("LNT_ConfigureRouterInfo: router address string [%s]", m->UPnPRouterAddressString);
806 break;
807 }
808 ptr++; // continue
809 }
810
811 // find port and router URL, starting after the "http://" if it was there
812 while (ptr && ptr != end)
813 {
814 if (*ptr == ':') // found the port number
815 {
816 int port;
817 ptr++; // skip over ':'
818 if (ptr == end) { LogOperation("LNT_ConfigureRouterInfo: reached end of buffer and no address!"); return; }
819 port = (int)strtol(ptr, (char **)mDNSNULL, 10); // get the port
820 m->UPnPRouterPort = mDNSOpaque16fromIntVal(port); // store it properly converted
821 }
822 else if (*ptr == '/') // found router URL
823 {
824 int j;
825 char *urlPtr;
826 m->UPnPInterfaceID = InterfaceID;
827 if (mDNSIPPortIsZero(m->UPnPRouterPort)) m->UPnPRouterPort = mDNSOpaque16fromIntVal(80); // fill in default port if we didn't find one before
828
829 urlPtr = ptr;
830 for (j = 0; urlPtr && urlPtr != end; j++, urlPtr++) if (*urlPtr == '\r') break; // first find the end of the line and count the chars
831 if (urlPtr == mDNSNULL || urlPtr == end) return; // not a valid message
832
833 // allocate the buffer (len j+1 so we have space to terminate the string)
834 if (m->UPnPRouterURL != mDNSNULL) mDNSPlatformMemFree(m->UPnPRouterURL);
835 if ((m->UPnPRouterURL = (mDNSu8 *) mDNSPlatformMemAllocate(j+1)) == mDNSNULL) { LogMsg("can't allocate router URL"); return; }
836
837 // now copy everything to the end of the line
838 strncpy((char *)m->UPnPRouterURL, ptr, j); // this URL looks something like "/dyndev/uuid:0013-108c-4b3f0000f3dc"
839 m->UPnPRouterURL[j] = '\0'; // terminate the string
840 break; // we've got everything we need, so get out here
841 }
842 ptr++; // continue
843 }
844
845 if (ptr == mDNSNULL || ptr == end) return; // not a valid message
846 LogOperation("Router port %d, URL set to [%s]...", mDNSVal16(m->UPnPRouterPort), m->UPnPRouterURL);
847
848 // Don't need the SSDP socket anymore
849 if (m->SSDPSocket) { LogOperation("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
850
851 // now send message to get the device description
852 GetDeviceDescription(m, &m->tcpDeviceInfo);
853 }
854
855 mDNSexport void LNT_SendDiscoveryMsg(mDNS *m)
856 {
857 static const char msg[] =
858 "M-SEARCH * HTTP/1.1\r\n"
859 "Host:239.255.255.250:1900\r\n"
860 "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n"
861 "Man:\"ssdp:discover\"\r\n"
862 "MX:3\r\n\r\n";
863 static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } };
864
865 mDNSu8* buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
866 unsigned int bufLen;
867
868 // Always query for WANIPConnection in the first SSDP packet
869 if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse;
870
871 // Create message
872 bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP");
873
874 LogOperation("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExternalAddress);
875
876 if (!mDNSIPv4AddressIsZero(m->Router.ip.v4) && mDNSIPv4AddressIsZero(m->ExternalAddress))
877 {
878 if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(m, zeroIPPort); LogOperation("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); }
879 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router, SSDPPort);
880 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort);
881 }
882
883 m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection;
884 }
885
886 #endif /* _LEGACY_NAT_TRAVERSAL_ */