1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2004-2015 Apple Inc. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #ifdef _LEGACY_NAT_TRAVERSAL_
20 #include "stdlib.h" // For strtol()
21 #include "string.h" // For strlcpy(), For strncpy(), strncasecmp()
22 #include "assert.h" // For assert()
25 # include "CommonServices.h"
26 # include <winsock2.h>
27 # include <ws2tcpip.h>
28 # define strcasecmp _stricmp
29 # define strncasecmp _strnicmp
30 # define mDNSASLLog( UUID, SUBDOMAIN, RESULT, SIGNATURE, FORMAT, ... ) ;
33 inet_pton( int family, const char * addr, void * dst )
35 struct sockaddr_storage ss;
36 int sslen = sizeof( ss );
38 ZeroMemory( &ss, sizeof( ss ) );
39 ss.ss_family = (ADDRESS_FAMILY)family;
41 if ( WSAStringToAddressA( (LPSTR)addr, family, NULL, ( struct sockaddr* ) &ss, &sslen ) == 0 )
43 if ( family == AF_INET ) { memcpy( dst, &( ( struct sockaddr_in* ) &ss)->sin_addr, sizeof( IN_ADDR ) ); return 1; }
44 else if ( family == AF_INET6 ) { memcpy( dst, &( ( struct sockaddr_in6* ) &ss)->sin6_addr, sizeof( IN6_ADDR ) ); return 1; }
50 # include <arpa/inet.h> // For inet_pton()
53 #include "mDNSEmbeddedAPI.h"
54 #include "uDNS.h" // For natTraversalHandleAddressReply() etc.
56 // used to format SOAP port mapping arguments
57 typedef struct Property_struct
64 // All of the text parsing in this file is intentionally transparent so that we know exactly
65 // what's being done to the text, with an eye towards preventing security problems.
67 // This is an evolving list of useful acronyms to know. Please add to it at will.
69 // NT Notification Type
70 // USN Unique Service Name
71 // UDN Unique Device Name
72 // UUID Universally Unique Identifier
73 // URN/urn Universal Resource Name
75 // Forward declaration because of circular reference:
76 // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse
77 // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again
78 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n);
80 #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (mDNSu16)(n)->tcpInfo.retries)
82 // Note that this function assumes src is already NULL terminated
83 mDNSlocal void AllocAndCopy(mDNSu8 **const dst, const mDNSu8 *const src)
85 if (src == mDNSNULL) return;
86 if ((strlen((char*)src)) >= UINT32_MAX || (*dst = mDNSPlatformMemAllocate((mDNSu32)strlen((char*)src) + 1)) == mDNSNULL)
88 LogMsg("AllocAndCopy: can't allocate string");
91 strcpy((char*)*dst, (char*)src);
94 // This function does a simple parse of an HTTP URL that may include a hostname, port, and path
95 // If found in the URL, addressAndPort and path out params will point to newly allocated space (and will leak if they were previously pointing at allocated space)
96 mDNSlocal mStatus ParseHttpUrl(const mDNSu8 *ptr, const mDNSu8 *const end, mDNSu8 **const addressAndPort, mDNSIPPort *const port, mDNSu8 **const path)
98 // if the data begins with "http://", we assume there is a hostname and possibly a port number
99 if (end - ptr >= 7 && strncasecmp((char*)ptr, "http://", 7) == 0)
102 const mDNSu8 *stop = end;
103 const mDNSu8 *addrPtr = mDNSNULL;
105 ptr += 7; //skip over "http://"
106 if (ptr >= end) { LogInfo("ParseHttpUrl: past end of buffer parsing host:port"); return mStatus_BadParamErr; }
108 // find the end of the host:port
110 for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break;
112 // allocate the buffer (len i+1 so we have space to terminate the string)
113 if ((*addressAndPort = mDNSPlatformMemAllocate(i+1)) == mDNSNULL)
114 { LogMsg("ParseHttpUrl: can't allocate address string"); return mStatus_NoMemoryErr; }
115 strncpy((char*)*addressAndPort, (char*)ptr, i);
116 (*addressAndPort)[i] = '\0';
118 // find the port number in the string, by looking backwards for the ':'
119 stop = ptr; // can't go back farther than the original start
120 ptr = addrPtr; // move ptr to the path part
122 for (addrPtr--; addrPtr>stop; addrPtr--)
126 addrPtr++; // skip over ':'
127 *port = mDNSOpaque16fromIntVal((mDNSu16)strtol((char*)addrPtr, mDNSNULL, 10)); // store it properly converted
133 // ptr should now point to the first character we haven't yet processed
134 // everything that remains is the path
135 if (path && ptr < end)
137 if ((*path = mDNSPlatformMemAllocate((mDNSu32)(end - ptr) + 1)) == mDNSNULL)
138 { LogMsg("ParseHttpUrl: can't mDNSPlatformMemAllocate path"); return mStatus_NoMemoryErr; }
139 strncpy((char*)*path, (char*)ptr, end - ptr);
140 (*path)[end - ptr] = '\0';
143 return mStatus_NoError;
148 HTTPCode_NeedMoreData = -1, // No code found in stream
149 HTTPCode_Other = -2, // Valid code other than those below found in stream
156 mDNSlocal mDNSs16 ParseHTTPResponseCode(const mDNSu8 **const data, const mDNSu8 *const end)
158 const mDNSu8 *ptr = *data;
161 if (end - ptr < 5) return HTTPCode_NeedMoreData;
162 if (strncasecmp((char*)ptr, "HTTP/", 5) != 0) return HTTPCode_Bad;
164 // should we care about the HTTP protocol version?
166 // look for first space, which must come before first LF
167 while (ptr && ptr != end)
169 if (*ptr == '\n') return HTTPCode_Bad;
170 if (*ptr == ' ') break;
173 if (ptr == end) return HTTPCode_NeedMoreData;
176 if (end - ptr < 3) return HTTPCode_NeedMoreData;
180 while (ptr && ptr != end)
182 if (*ptr == '\n') break;
185 if (ptr == end) return HTTPCode_NeedMoreData;
188 if (memcmp((char*)code, "200", 3) == 0) return HTTPCode_200;
189 if (memcmp((char*)code, "404", 3) == 0) return HTTPCode_404;
190 if (memcmp((char*)code, "500", 3) == 0) return HTTPCode_500;
192 LogInfo("ParseHTTPResponseCode found unexpected result code: %c%c%c", code[0], code[1], code[2]);
193 return HTTPCode_Other;
196 // This function parses the xml body of the device description response from the router. Basically, we look to
197 // make sure this is a response referencing a service we care about (WANIPConnection or WANPPPConnection),
198 // look for the "controlURL" header immediately following, and copy the addressing and URL info we need
199 mDNSlocal void handleLNTDeviceDescriptionResponse(tcpLNTInfo *tcpInfo)
201 mDNS *m = tcpInfo->m;
202 const mDNSu8 *ptr = tcpInfo->Reply;
203 const mDNSu8 *end = tcpInfo->Reply + tcpInfo->nread;
207 if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return; // already have the info we need
209 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
210 if (http_result == HTTPCode_404) LNT_ClearState(m);
211 if (http_result != HTTPCode_200)
213 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "noop", "HTTP Result", "HTTP code: %d", http_result);
217 // Always reset our flag to use WANIPConnection. We'll use WANPPPConnection if we find it and don't find WANIPConnection.
218 m->UPnPWANPPPConnection = mDNSfalse;
220 // find either service we care about
221 while (ptr && ptr < end)
223 if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANIPConnection:1", 17) == 0)) break;
228 ptr = tcpInfo->Reply;
229 while (ptr && ptr < end)
231 if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANPPPConnection:1", 18) == 0))
233 m->UPnPWANPPPConnection = mDNStrue;
239 if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; }
241 // find "controlURL", starting from where we left off
242 while (ptr && ptr < end)
244 if ((*ptr & 0xDF) == 'C' && (strncasecmp((char*)ptr, "controlURL", 10) == 0)) break; // find the first 'c'; is this controlURL? if not, keep looking
247 if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; }
248 ptr += 11; // skip over "controlURL>"
249 if (ptr >= end) { LogInfo("handleLNTDeviceDescriptionResponse: past end of buffer and no body!"); return; } // check ptr again in case we skipped over the end of the buffer
251 // find the end of the controlURL element
252 for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } }
254 // fill in default port
255 m->UPnPSOAPPort = m->UPnPRouterPort;
257 // free string pointers and set to NULL
258 if (m->UPnPSOAPAddressString != mDNSNULL)
260 mDNSPlatformMemFree(m->UPnPSOAPAddressString);
261 m->UPnPSOAPAddressString = mDNSNULL;
263 if (m->UPnPSOAPURL != mDNSNULL)
265 mDNSPlatformMemFree(m->UPnPSOAPURL);
266 m->UPnPSOAPURL = mDNSNULL;
269 if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, &m->UPnPSOAPURL) != mStatus_NoError) return;
270 // the SOAPURL should look something like "/uuid:0013-108c-4b3f0000f3dc"
272 if (m->UPnPSOAPAddressString == mDNSNULL)
274 ptr = tcpInfo->Reply;
275 while (ptr && ptr < end)
277 if ((*ptr & 0xDF) == 'U' && (strncasecmp((char*)ptr, "URLBase", 7) == 0)) break;
281 if (ptr < end) // found URLBase
283 LogInfo("handleLNTDeviceDescriptionResponse: found URLBase");
284 ptr += 8; // skip over "URLBase>"
285 // find the end of the URLBase element
286 for (stop = ptr; stop < end; stop++) { if (stop && *stop == '<') { end = stop; break; } }
287 if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, mDNSNULL) != mStatus_NoError)
289 LogInfo("handleLNTDeviceDescriptionResponse: failed to parse URLBase");
293 // if all else fails, use the router address string
294 if (m->UPnPSOAPAddressString == mDNSNULL) AllocAndCopy(&m->UPnPSOAPAddressString, m->UPnPRouterAddressString);
296 if (m->UPnPSOAPAddressString == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPAddressString is NULL");
297 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m->UPnPSOAPAddressString);
299 if (m->UPnPSOAPURL == mDNSNULL) AllocAndCopy(&m->UPnPSOAPURL, m->UPnPRouterURL);
300 if (m->UPnPSOAPURL == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPURL is NULL");
301 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP URL [%s]", m->UPnPSOAPURL);
304 mDNSlocal void handleLNTGetExternalAddressResponse(tcpLNTInfo *tcpInfo)
306 mDNS *m = tcpInfo->m;
307 mDNSu16 err = NATErr_None;
309 const mDNSu8 *ptr = tcpInfo->Reply;
310 const mDNSu8 *end = tcpInfo->Reply + tcpInfo->nread;
312 static char tagname[20] = { 'N','e','w','E','x','t','e','r','n','a','l','I','P','A','d','d','r','e','s','s' };
313 // Array NOT including a terminating nul
315 // LogInfo("handleLNTGetExternalAddressResponse: %s", ptr);
317 mDNSs16 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
318 if (http_result == HTTPCode_404) LNT_ClearState(m);
319 if (http_result != HTTPCode_200)
321 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "HTTP Result", "HTTP code: %d", http_result);
325 while (ptr < end && strncasecmp((char*)ptr, tagname, sizeof(tagname))) ptr++;
326 ptr += sizeof(tagname); // Skip over "NewExternalIPAddress"
327 while (ptr < end && *ptr != '>') ptr++;
328 ptr += 1; // Skip over ">"
330 // Find the end of the address and terminate the string so inet_pton() can convert it
331 // (Might be better to copy this to a local string here -- this is overwriting tcpInfo->Reply in-place
332 addrend = (mDNSu8*)ptr;
333 while (addrend < end && (mDNSIsDigit(*addrend) || *addrend == '.')) addrend++;
334 if (addrend >= end) return;
337 if (inet_pton(AF_INET, (char*)ptr, &ExtAddr) <= 0)
339 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "inet_pton", "");
340 LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr);
341 err = NATErr_NetFail;
342 ExtAddr = zerov4Addr;
344 if (!err) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr);
346 natTraversalHandleAddressReply(m, err, ExtAddr);
349 mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo)
351 mDNS *m = tcpInfo->m;
352 mDNSIPPort extport = zeroIPPort;
353 const mDNSu8 *ptr = tcpInfo->Reply;
354 const mDNSu8 *const end = tcpInfo->Reply + tcpInfo->nread;
355 NATTraversalInfo *natInfo;
358 for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break;}
360 if (!natInfo) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; }
362 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
363 if (http_result == HTTPCode_200)
365 LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)",
366 mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries);
368 // Make sure to compute extport *before* we zero tcpInfo->retries
369 extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo));
370 tcpInfo->retries = 0;
371 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE, NATTProtocolUPNPIGD);
373 else if (http_result == HTTPCode_500)
375 while (ptr && ptr != end)
377 if (((*ptr & 0xDF) == 'C' && end - ptr >= 8 && strncasecmp((char*)ptr, "Conflict", 8) == 0) ||
378 (*ptr == '>' && end - ptr >= 15 && strncasecmp((char*)ptr, ">718</errorCode", 15) == 0))
380 if (tcpInfo->retries < 100)
382 tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo);
383 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict", "Retry %d", tcpInfo->retries);
387 LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort));
388 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict - too many retries", "Retries: %d", tcpInfo->retries);
389 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0, NATTProtocolUPNPIGD);
396 else if (http_result == HTTPCode_Bad) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response");
397 else if (http_result == HTTPCode_Other) LogMsg("handleLNTPortMappingResponse got unexpected response code");
398 else if (http_result == HTTPCode_404) LNT_ClearState(m);
399 if (http_result != HTTPCode_200 && http_result != HTTPCode_500)
400 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "HTTP Result", "HTTP code: %d", http_result);
403 mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo)
405 tcpLNTInfo **ptr = &m->tcpInfoUnmapList;
406 while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next;
407 if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); } // If we found it, cut it from our list and free the memory
410 mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err)
412 mStatus status = mStatus_NoError;
413 tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context;
414 mDNSBool closed = mDNSfalse;
417 static int LNTERRORcount = 0;
419 if (tcpInfo->sock != sock)
421 LogMsg("tcpConnectionCallback: WARNING- tcpInfo->sock(%p) != sock(%p) !!! Printing tcpInfo struct", tcpInfo->sock, sock);
422 LogMsg("tcpConnectionCallback: tcpInfo->Address:Port [%#a:%d] tcpInfo->op[%d] tcpInfo->retries[%d] tcpInfo->Request[%s] tcpInfo->Reply[%s]",
423 &tcpInfo->Address, mDNSVal16(tcpInfo->Port), tcpInfo->op, tcpInfo->retries, tcpInfo->Request, tcpInfo->Reply);
426 // The handlers below expect to be called with the lock held
427 mDNS_Lock(tcpInfo->m);
429 if (err) { LogInfo("tcpConnectionCallback: received error"); goto exit; }
431 if (ConnectionEstablished) // connection is established - send the message
433 LogInfo("tcpConnectionCallback: connection established, sending message");
434 nsent = mDNSPlatformWriteTCP(sock, (char*)tcpInfo->Request, tcpInfo->requestLen);
435 if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; }
439 n = mDNSPlatformReadTCP(sock, (char*)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed);
440 LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n);
442 if (n < 0) { LogInfo("tcpConnectionCallback - read returned %d", n); status = mStatus_ConnFailed; goto exit; }
443 else if (closed) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; }
446 LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread);
447 if (tcpInfo->nread > LNT_MAXBUFSIZE)
449 LogInfo("result truncated...");
450 tcpInfo->nread = LNT_MAXBUFSIZE;
455 case LNTDiscoveryOp: handleLNTDeviceDescriptionResponse (tcpInfo); break;
456 case LNTExternalAddrOp: handleLNTGetExternalAddressResponse(tcpInfo); break;
457 case LNTPortMapOp: handleLNTPortMappingResponse (tcpInfo); break;
458 case LNTPortMapDeleteOp: status = mStatus_ConfigChanged; break;
459 default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break;
465 mDNS *m = tcpInfo->m;
466 if ((++LNTERRORcount % 1000) == 0)
468 LogMsg("ERROR: tcpconnectioncallback -> got error status %d times", LNTERRORcount);
469 assert(LNTERRORcount < 1000);
470 // Recovery Mechanism to bail mDNSResponder out of trouble: It has been seen that we can get into
471 // this loop: [tcpKQSocketCallback()--> doTcpSocketCallback()-->tcpconnectionCallback()-->mDNSASLLog()],
472 // if mDNSPlatformTCPCloseConnection() does not close the TCPSocket. Instead of calling mDNSASLLog()
473 // repeatedly and logging the same error msg causing 100% CPU usage, we
474 // crash mDNSResponder using assert() and restart fresh. See advantages below:
475 // 1.Better User Experience
476 // 2.CrashLogs frequency can be monitored
477 // 3.StackTrace can be used for more info
482 case LNTDiscoveryOp: if (m->UPnPSOAPAddressString == mDNSNULL)
483 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP Address", "");
484 if (m->UPnPSOAPURL == mDNSNULL)
485 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP path", "");
486 if (m->UPnPSOAPAddressString && m->UPnPSOAPURL)
487 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "success", "success", "");
489 case LNTExternalAddrOp: mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest",
490 mDNSIPv4AddressIsZero(m->ExtAddress) ? "failure" : "success",
491 mDNSIPv4AddressIsZero(m->ExtAddress) ? "failure" : "success", "");
493 case LNTPortMapOp: if (tcpInfo->parentNATInfo)
494 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", (tcpInfo->parentNATInfo->Result) ? "failure" : "success",
495 (tcpInfo->parentNATInfo->Result) ? "failure" : "success", "Result: %d", tcpInfo->parentNATInfo->Result);
497 case LNTPortMapDeleteOp: break;
501 mDNSPlatformTCPCloseConnection(sock);
502 tcpInfo->sock = mDNSNULL;
503 if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; }
504 if (tcpInfo->Reply ) { mDNSPlatformMemFree(tcpInfo->Reply); tcpInfo->Reply = mDNSNULL; }
508 LNTERRORcount = 0; // clear LNTERRORcount
511 if (tcpInfo) mDNS_Unlock(tcpInfo->m);
513 if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo);
516 mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op)
518 mStatus err = mStatus_NoError;
519 mDNSIPPort srcport = zeroIPPort;
521 if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port))
522 { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); }
524 info->Address = *Addr;
528 info->replyLen = LNT_MAXBUFSIZE;
529 if (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE); // reuse previously allocated buffer
530 else if ((info->Reply = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr); }
532 if (info->sock) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; }
533 info->sock = mDNSPlatformTCPSocket(m, kTCPSocketFlags_Zero, &srcport, mDNSfalse);
534 if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); }
535 LogInfo("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port));
536 err = mDNSPlatformTCPConnect(info->sock, Addr, Port, mDNSNULL, 0, tcpConnectionCallback, info);
538 if (err == mStatus_ConnPending) err = mStatus_NoError;
539 else if (err == mStatus_ConnEstablished)
541 mDNS_DropLockBeforeCallback();
542 tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError);
543 mDNS_ReclaimLockAfterCallback();
544 err = mStatus_NoError;
548 // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc.
549 LogInfo("LNT MakeTCPConnection: connection failed");
550 mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above
551 info->sock = mDNSNULL;
552 mDNSPlatformMemFree(info->Reply);
553 info->Reply = mDNSNULL;
558 mDNSlocal unsigned int AddSOAPArguments(char *const buf, const unsigned int maxlen, const int numArgs, const Property *const a)
560 static const char f1[] = "<%s>%s</%s>";
561 static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>";
564 for (i = 0; i < numArgs; i++)
566 if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name);
567 else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name, a[i].value, a[i].name);
572 mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, const char *const Action, const int numArgs, const Property *const Arguments, const LNTOp_t op)
574 // SOAP message header format -
577 // - router's host/port ("host:port")
579 static const char header[] =
580 "POST %s HTTP/1.1\r\n"
581 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
582 "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n"
583 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n"
585 "Content-Length: %d\r\n"
586 "Connection: close\r\n"
587 "Pragma: no-cache\r\n"
591 static const char body1[] =
592 "<?xml version=\"1.0\"?>\r\n"
594 " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
595 " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
597 "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">";
599 static const char body2[] =
602 "</SOAP-ENV:Envelope>\r\n";
605 char *body = (char*)&m->omsg; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty
608 if (mDNSIPPortIsZero(m->UPnPSOAPPort) || m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL) // if no SOAP URL or address exists get out here
609 { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid; }
612 bodyLen = mDNS_snprintf (body, sizeof(m->omsg), body1, Action, m->UPnPWANPPPConnection ? "PPP" : "IP");
613 bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments);
614 bodyLen += mDNS_snprintf (body + bodyLen, sizeof(m->omsg) - bodyLen, body2, Action);
616 // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field
617 if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE);
618 if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; }
619 info->requestLen = mDNS_snprintf((char*)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body);
621 err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op);
622 if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; }
626 // Build port mapping request with new port (up to max) and send it
627 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n)
629 char externalPort[6];
630 char internalPort[6];
631 char localIPAddrString[30];
632 char publicPortString[40];
633 Property propArgs[8];
634 mDNSu16 ReqPortNum = RequestedPortNum(n);
635 NATTraversalInfo *n2 = m->NATTraversals;
637 // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique.
638 // UPnP gateways will report conflicts if different devices request the same external port, but if two
639 // clients on the same device request the same external port the second one just stomps over the first.
640 // One way this can happen is like this:
641 // 1. Client A binds local port 80
642 // 2. Client A requests external port 80 -> internal port 80
643 // 3. UPnP NAT gateway refuses external port 80 (some other client already has it)
644 // 4. Client A tries again, and successfully gets external port 80 -> internal port 81
645 // 5. Client B on same machine tries to bind local port 80, and fails
646 // 6. Client B tries again, and successfully binds local port 81
647 // 7. Client B now requests external port 81 -> internal port 81
648 // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping
652 if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next;
655 if (n->tcpInfo.retries < 100)
657 n->tcpInfo.retries++;
658 ReqPortNum = RequestedPortNum(n); // Pick a new port number
659 n2 = m->NATTraversals; // And re-scan the list looking for conflicts
663 natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0, NATTProtocolUPNPIGD);
664 return mStatus_NoError;
669 // create strings to use in the message
670 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", ReqPortNum);
671 mDNS_snprintf(internalPort, sizeof(internalPort), "%u", mDNSVal16(n->IntPort));
672 mDNS_snprintf(publicPortString, sizeof(publicPortString), "iC%u", ReqPortNum);
673 mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u",
674 m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]);
677 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
678 propArgs[0].name = "NewRemoteHost";
679 propArgs[0].type = "string";
680 propArgs[0].value = "";
681 propArgs[1].name = "NewExternalPort";
682 propArgs[1].type = "ui2";
683 propArgs[1].value = externalPort;
684 propArgs[2].name = "NewProtocol";
685 propArgs[2].type = "string";
686 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
687 propArgs[3].name = "NewInternalPort";
688 propArgs[3].type = "ui2";
689 propArgs[3].value = internalPort;
690 propArgs[4].name = "NewInternalClient";
691 propArgs[4].type = "string";
692 propArgs[4].value = localIPAddrString;
693 propArgs[5].name = "NewEnabled";
694 propArgs[5].type = "boolean";
695 propArgs[5].value = "1";
696 propArgs[6].name = "NewPortMappingDescription";
697 propArgs[6].type = "string";
698 propArgs[6].value = publicPortString;
699 propArgs[7].name = "NewLeaseDuration";
700 propArgs[7].type = "ui4";
701 propArgs[7].value = "0";
703 LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum);
704 return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp);
707 mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *const n)
709 LogInfo("LNT_MapPort");
710 if (n->tcpInfo.sock) return(mStatus_NoError); // If we already have a connection up don't make another request for the same thing
711 n->tcpInfo.parentNATInfo = n;
712 n->tcpInfo.retries = 0;
713 return SendPortMapRequest(m, n);
716 mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *const n)
718 char externalPort[10];
719 Property propArgs[3];
721 tcpLNTInfo **infoPtr = &m->tcpInfoUnmapList;
724 // If no NAT gateway to talk to, no need to do all this work for nothing
725 if (mDNSIPPortIsZero(m->UPnPSOAPPort) || !m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError;
727 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort));
729 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
730 propArgs[0].name = "NewRemoteHost";
731 propArgs[0].type = "string";
732 propArgs[0].value = "";
733 propArgs[1].name = "NewExternalPort";
734 propArgs[1].type = "ui2";
735 propArgs[1].value = externalPort;
736 propArgs[2].name = "NewProtocol";
737 propArgs[2].type = "string";
738 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
740 n->tcpInfo.parentNATInfo = n;
742 // clean up previous port mapping requests and allocations
743 if (n->tcpInfo.sock) LogInfo("LNT_UnmapPort: closing previous open connection");
744 if (n->tcpInfo.sock ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock = mDNSNULL; }
745 if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request); n->tcpInfo.Request = mDNSNULL; }
746 if (n->tcpInfo.Reply ) { mDNSPlatformMemFree(n->tcpInfo.Reply); n->tcpInfo.Reply = mDNSNULL; }
748 // 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)
749 if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL)
750 { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); }
753 while (*infoPtr) infoPtr = &(*infoPtr)->next; // find the end of the list
754 *infoPtr = info; // append
756 err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp);
757 if (err) DisposeInfoFromUnmapList(m, info);
761 mDNSexport mStatus LNT_GetExternalAddress(mDNS *m)
763 return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp);
766 mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info)
768 // Device description format -
769 // - device description URL
771 static const char szSSDPMsgDescribeDeviceFMT[] =
772 "GET %s HTTP/1.1\r\n"
773 "Accept: text/xml, application/xml\r\n"
774 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n"
776 "Connection: close\r\n"
779 if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return mStatus_NoError; // already have the info we need
781 if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); }
784 if (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer
785 else if ((info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); }
786 info->requestLen = mDNS_snprintf((char*)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString);
787 LogInfo("Describe Device: [%s]", info->Request);
788 return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp);
791 // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response
792 // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and
794 mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, const mDNSu8 *const data, const mDNSu16 len)
796 const mDNSu8 *ptr = data;
797 const mDNSu8 *end = data + len;
800 if (!mDNSIPPortIsZero(m->UPnPRouterPort)) return; // already have the info we need
802 // The formatting of the HTTP header is not always the same when it comes to the placement of
803 // the service and location strings, so we just look for each of them from the beginning for every response
805 // figure out if this is a message from a service we care about
806 while (ptr && ptr != end)
808 if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANIPConnection:1", 17) == 0)) break;
814 while (ptr && ptr != end)
816 if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANPPPConnection:1", 18) == 0)) break;
820 if (ptr == mDNSNULL || ptr == end) return; // not a message we care about
822 // find "Location:", starting from the beginning
824 while (ptr && ptr != end)
826 if ((*ptr & 0xDF) == 'L' && (strncasecmp((char*)ptr, "Location:", 9) == 0)) break; // find the first 'L'; is this Location? if not, keep looking
829 if (ptr == mDNSNULL || ptr == end)
831 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Location", "");
832 return; // not a message we care about
834 ptr += 9; //Skip over 'Location:'
835 while (*ptr == ' ' && ptr < end) ptr++; // skip over spaces
836 if (ptr >= end) return;
838 // find the end of the line
839 for (stop = ptr; stop != end; stop++) { if (*stop == '\r') { end = stop; break; } }
841 // fill in default port
842 m->UPnPRouterPort = mDNSOpaque16fromIntVal(80);
844 // free string pointers and set to NULL
845 if (m->UPnPRouterAddressString != mDNSNULL)
847 mDNSPlatformMemFree(m->UPnPRouterAddressString);
848 m->UPnPRouterAddressString = mDNSNULL;
850 if (m->UPnPRouterURL != mDNSNULL)
852 mDNSPlatformMemFree(m->UPnPRouterURL);
853 m->UPnPRouterURL = mDNSNULL;
856 // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc"
857 if (ParseHttpUrl(ptr, end, &m->UPnPRouterAddressString, &m->UPnPRouterPort, &m->UPnPRouterURL) != mStatus_NoError)
859 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Parse URL", "");
863 m->UPnPInterfaceID = InterfaceID;
865 if (m->UPnPRouterAddressString == mDNSNULL)
867 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router address", "");
868 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL");
870 else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m->UPnPRouterAddressString);
872 if (m->UPnPRouterURL == mDNSNULL)
874 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router path", "");
875 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL");
877 else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m->UPnPRouterURL);
879 LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m->UPnPRouterPort));
880 LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m->UPnPInterfaceID);
882 // Don't need the SSDP socket anymore
883 if (m->SSDPSocket) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
885 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "success", "success", "");
886 // now send message to get the device description
887 GetDeviceDescription(m, &m->tcpDeviceInfo);
890 mDNSexport void LNT_SendDiscoveryMsg(mDNS *m)
892 static const char msg[] =
893 "M-SEARCH * HTTP/1.1\r\n"
894 "Host:239.255.255.250:1900\r\n"
895 "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n"
896 "Man:\"ssdp:discover\"\r\n"
898 static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } };
900 mDNSu8 *buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
903 if (!mDNSIPPortIsZero(m->UPnPRouterPort))
905 if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
906 if (mDNSIPPortIsZero(m->UPnPSOAPPort) && !m->tcpDeviceInfo.sock) GetDeviceDescription(m, &m->tcpDeviceInfo);
910 // Always query for WANIPConnection in the first SSDP packet
911 if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse;
914 bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP");
916 debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExtAddress);
918 if (!mDNSIPv4AddressIsZero(m->Router.ip.v4))
920 if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(m, zeroIPPort); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); }
921 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router, SSDPPort, mDNSfalse);
922 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort, mDNSfalse);
925 m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection;
928 mDNSexport void LNT_ClearState(mDNS *const m)
930 if (m->tcpAddrInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpAddrInfo.sock); m->tcpAddrInfo.sock = mDNSNULL; }
931 if (m->tcpDeviceInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpDeviceInfo.sock); m->tcpDeviceInfo.sock = mDNSNULL; }
932 m->UPnPSOAPPort = m->UPnPRouterPort = zeroIPPort; // Reset UPnP ports
935 #endif /* _LEGACY_NAT_TRAVERSAL_ */