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