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