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