| /* -*- Mode: C; tab-width: 4 -*- |
| * |
| * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| |
| Change History (most recent first): |
| |
| $Log: LegacyNATTraversal.c,v $ |
| Revision 1.65 2009/07/03 03:16:07 jessic2 |
| <rdar://problem/7026146> BTMM: UPnP works in Leopard but doesn't work in SnowLeopard (URLBase is empty) Made changes to support the case where the URLBase tag exists but there isn't a valid URL |
| |
| Revision 1.64 2009/06/25 21:07:44 herscher |
| <rdar://problem/4147784> B4W should support UPnP |
| |
| Revision 1.63 2009/03/26 03:59:00 jessic2 |
| Changes for <rdar://problem/6492552&6492593&6492609&6492613&6492628&6492640&6492699> |
| |
| Revision 1.62 2009/02/13 06:31:09 cheshire |
| Converted LogOperation messages to LogInfo |
| |
| Revision 1.61 2009/01/23 19:25:43 mcguire |
| <rdar://problem/6514439> UPnP: Should not use NATErr_Refused when too many conflict retries |
| |
| Revision 1.60 2009/01/23 00:38:36 mcguire |
| <rdar://problem/5570906> BTMM: Doesn't work with Linksys WRT54GS firmware 4.71.1 |
| |
| Revision 1.59 2009/01/22 20:32:17 mcguire |
| <rdar://problem/6446934> BTMM: pref pane reports enabled but negotiation failed |
| Make sure we push the pointer out past the LF if we read it. |
| |
| Revision 1.58 2009/01/22 01:15:58 mcguire |
| <rdar://problem/6446934> BTMM: pref pane reports enabled but negotiation failed |
| |
| Revision 1.57 2008/12/19 21:09:22 mcguire |
| <rdar://problem/6431147> UPnP: error messages when canceling seemingly unrelated browse |
| |
| Revision 1.56 2008/12/06 01:42:57 mcguire |
| <rdar://problem/6418958> Need to exponentially back-off after failure to get public address |
| |
| Revision 1.55 2008/12/01 19:43:48 mcguire |
| <rdar://problem/6404766> UPnP: Handle errorCode 718 as a conflict when requesting a port mapping |
| |
| Revision 1.54 2008/11/26 20:57:37 cheshire |
| For consistency with other similar macros, renamed mdnsIsDigit/mdnsIsLetter/mdnsValidHostChar |
| to mDNSIsDigit/mDNSIsLetter/mDNSValidHostChar |
| |
| Revision 1.53 2008/11/26 20:34:04 cheshire |
| Changed "destroying SSDPSocket" LogOperation debugging messages to debugf |
| |
| Revision 1.52 2008/11/26 19:54:03 cheshire |
| Changed some "LogOperation" debugging messages to "debugf" |
| |
| Revision 1.51 2008/11/20 02:23:38 mcguire |
| <rdar://problem/6041208> need to handle URLBase |
| |
| Revision 1.50 2008/09/20 00:34:22 mcguire |
| <rdar://problem/6129039> BTMM: Add support for WANPPPConnection |
| |
| Revision 1.49 2008/08/07 21:51:13 mcguire |
| <rdar://problem/5904423> UPnP: Possible memory corruption bug |
| <rdar://problem/5930173> UPnP: Combine URL parsing code |
| |
| Revision 1.48 2008/07/24 20:23:04 cheshire |
| <rdar://problem/3988320> Should use randomized source ports and transaction IDs to avoid DNS cache poisoning |
| |
| Revision 1.47 2008/07/18 21:37:46 mcguire |
| <rdar://problem/5736845> BTMM: alternate SSDP queries between multicast & unicast |
| |
| Revision 1.46 2008/05/13 01:51:12 mcguire |
| <rdar://problem/5839161> UPnP compatibility workaround for Netgear WGT624 |
| |
| Revision 1.45 2007/12/06 00:22:27 mcguire |
| <rdar://problem/5604567> BTMM: Doesn't work with Linksys WAG300N 1.01.06 (sending from 1026/udp) |
| |
| Revision 1.44 2007/11/02 20:45:40 cheshire |
| Don't log "connection failed" in customer builds |
| |
| Revision 1.43 2007/10/18 20:09:47 cheshire |
| <rdar://problem/5545930> BTMM: Back to My Mac not working with D-Link DGL-4100 NAT gateway |
| |
| Revision 1.42 2007/10/16 17:37:18 cheshire |
| <rdar://problem/3557903> Performance: Core code will not work on platforms with small stacks |
| Cut SendSOAPMsgControlAction stack from 2144 to 96 bytes |
| |
| Revision 1.41 2007/10/15 23:02:00 cheshire |
| Off-by-one error: Incorrect trailing zero byte on the end of the SSDP Discovery message |
| |
| Revision 1.40 2007/09/20 21:41:49 cheshire |
| <rdar://problem/5495568> Legacy NAT Traversal - unmap request failed with error -65549 |
| |
| Revision 1.39 2007/09/20 20:41:40 cheshire |
| Reordered functions in file, in preparation for following fix |
| |
| Revision 1.38 2007/09/18 21:42:30 cheshire |
| To reduce programming mistakes, renamed ExtPort to RequestedPort |
| |
| Revision 1.37 2007/09/14 21:26:09 cheshire |
| <rdar://problem/5482627> BTMM: Need to manually avoid port conflicts when using UPnP gateways |
| |
| Revision 1.36 2007/09/14 01:15:50 cheshire |
| Minor fixes for problems discovered in pre-submission testing |
| |
| Revision 1.35 2007/09/13 00:16:42 cheshire |
| <rdar://problem/5468706> Miscellaneous NAT Traversal improvements |
| |
| Revision 1.34 2007/09/12 23:03:08 cheshire |
| <rdar://problem/5476978> DNSServiceNATPortMappingCreate callback not giving correct interface index |
| |
| Revision 1.33 2007/09/12 19:22:20 cheshire |
| Variable renaming in preparation for upcoming fixes e.g. priv/pub renamed to intport/extport |
| Made NAT Traversal packet handlers take typed data instead of anonymous "mDNSu8 *" byte pointers |
| |
| Revision 1.32 2007/09/11 19:19:16 cheshire |
| Correct capitalization of "uPNP" to "UPnP" |
| |
| Revision 1.31 2007/09/10 22:14:16 cheshire |
| When constructing fake NATAddrReply or NATPortMapReply packet, need to calculate |
| plausible upseconds value or core logic will think NAT engine has been rebooted |
| |
| Revision 1.30 2007/09/05 20:46:17 cheshire |
| Tidied up alignment of code layout |
| |
| Revision 1.29 2007/08/03 20:18:01 vazquez |
| <rdar://problem/5382177> LegacyNATTraversal: reading out of bounds can lead to DoS |
| |
| Revision 1.28 2007/07/31 02:28:36 vazquez |
| <rdar://problem/3734269> NAT-PMP: Detect public IP address changes and base station reboot |
| |
| Revision 1.27 2007/07/30 23:17:03 vazquez |
| Since lease times are meaningless in UPnP, return NATMAP_DEFAULT_LEASE in UPnP port mapping reply |
| |
| Revision 1.26 2007/07/27 22:50:08 vazquez |
| Allocate memory for UPnP request and reply buffers instead of using arrays |
| |
| Revision 1.25 2007/07/27 20:33:44 vazquez |
| Make sure we clean up previous port mapping requests before starting an unmap |
| |
| Revision 1.24 2007/07/27 00:57:48 vazquez |
| If a tcp connection is already established for doing a port mapping, don't start it again |
| |
| Revision 1.23 2007/07/26 21:19:26 vazquez |
| Retry port mapping with incremented port number (up to max) in order to handle |
| port mapping conflicts on UPnP gateways |
| |
| Revision 1.22 2007/07/25 21:41:00 vazquez |
| Make sure we clean up opened sockets when there are network transitions and when changing |
| port mappings |
| |
| Revision 1.21 2007/07/25 03:05:03 vazquez |
| Fixes for: |
| <rdar://problem/5338913> LegacyNATTraversal: UPnP heap overflow |
| <rdar://problem/5338933> LegacyNATTraversal: UPnP stack buffer overflow |
| and a myriad of other security problems |
| |
| Revision 1.20 2007/07/16 20:15:10 vazquez |
| <rdar://problem/3867231> LegacyNATTraversal: Need complete rewrite |
| |
| Revision 1.19 2007/06/21 16:37:43 jgraessley |
| Bug #: 5280520 |
| Reviewed by: Stuart Cheshire |
| Additional changes to get this compiling on the embedded platform. |
| |
| Revision 1.18 2007/05/09 01:43:32 cheshire |
| <rdar://problem/5187028> Change sprintf and strcpy to their safer snprintf and strlcpy equivalents |
| |
| Revision 1.17 2007/02/27 02:48:25 cheshire |
| Parameter to LNT_GetPublicIP function is IPv4 address, not anonymous "mDNSOpaque32" object |
| |
| Revision 1.16 2006/08/14 23:24:39 cheshire |
| Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0 |
| |
| Revision 1.15 2006/07/05 23:30:57 cheshire |
| Rename LegacyNATInit() -> LNT_Init() |
| |
| Revision 1.14 2005/12/08 03:00:33 cheshire |
| <rdar://problem/4349971> Byte order bugs in Legacy NAT traversal code |
| |
| Revision 1.13 2005/09/07 18:23:05 ksekar |
| <rdar://problem/4151514> Off-by-one overflow in LegacyNATTraversal |
| |
| Revision 1.12 2005/07/22 21:36:16 ksekar |
| Fix GCC 4.0/Intel compiler warnings |
| |
| Revision 1.11 2004/12/03 03:34:20 ksekar |
| <rdar://problem/3882674> LegacyNATTraversal.c leaks threads |
| |
| Revision 1.10 2004/12/01 02:43:49 cheshire |
| Update copyright message |
| |
| Revision 1.9 2004/10/27 02:25:05 cheshire |
| <rdar://problem/3816029> Random memory smashing bug |
| |
| Revision 1.8 2004/10/27 02:17:21 cheshire |
| Turn off "safe_close: ERROR" error messages -- there are too many of them |
| |
| Revision 1.7 2004/10/26 21:15:40 cheshire |
| <rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0 |
| Additional fixes: Code should set fds to -1 after closing sockets. |
| |
| Revision 1.6 2004/10/26 20:59:20 cheshire |
| <rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0 |
| |
| Revision 1.5 2004/10/26 01:01:35 cheshire |
| Use "#if 0" instead of commenting out code |
| |
| Revision 1.4 2004/10/10 06:51:36 cheshire |
| Declared some strings "const" as appropriate |
| |
| Revision 1.3 2004/09/21 23:40:12 ksekar |
| <rdar://problem/3810349> mDNSResponder to return errors on NAT traversal failure |
| |
| Revision 1.2 2004/09/17 01:08:52 cheshire |
| Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h |
| The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces |
| declared in that file are ONLY appropriate to single-address-space embedded applications. |
| For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used. |
| |
| Revision 1.1 2004/08/18 17:35:41 ksekar |
| <rdar://problem/3651443>: Feature #9586: Need support for Legacy NAT gateways |
| */ |
| |
| #ifdef _LEGACY_NAT_TRAVERSAL_ |
| |
| #include "stdlib.h" // For strtol() |
| #include "string.h" // For strlcpy(), For strncpy(), strncasecmp() |
| |
| #if defined( WIN32 ) |
| # include <winsock2.h> |
| # include <ws2tcpip.h> |
| # define strcasecmp _stricmp |
| # define strncasecmp _strnicmp |
| # define mDNSASLLog( UUID, SUBDOMAIN, RESULT, SIGNATURE, FORMAT, ... ) ; |
| |
| static int |
| inet_pton( int family, const char * addr, void * dst ) |
| { |
| struct sockaddr_storage ss; |
| int sslen = sizeof( ss ); |
| |
| ZeroMemory( &ss, sizeof( ss ) ); |
| ss.ss_family = family; |
| |
| if ( WSAStringToAddressA( addr, family, NULL, ( struct sockaddr* ) &ss, &sslen ) == 0 ) |
| { |
| if ( family == AF_INET ) { memcpy( dst, &( ( struct sockaddr_in* ) &ss)->sin_addr, sizeof( IN_ADDR ) ); return 1; } |
| else if ( family == AF_INET6 ) { memcpy( dst, &( ( struct sockaddr_in6* ) &ss)->sin6_addr, sizeof( IN6_ADDR ) ); return 1; } |
| else return 0; |
| } |
| else return 0; |
| } |
| #else |
| # include <arpa/inet.h> // For inet_pton() |
| #endif |
| |
| #include "mDNSEmbeddedAPI.h" |
| #include "uDNS.h" // For natTraversalHandleAddressReply() etc. |
| |
| // used to format SOAP port mapping arguments |
| typedef struct Property_struct |
| { |
| char *name; |
| char *type; |
| char *value; |
| } Property; |
| |
| // All of the text parsing in this file is intentionally transparent so that we know exactly |
| // what's being done to the text, with an eye towards preventing security problems. |
| |
| // This is an evolving list of useful acronyms to know. Please add to it at will. |
| // ST Service Type |
| // NT Notification Type |
| // USN Unique Service Name |
| // UDN Unique Device Name |
| // UUID Universally Unique Identifier |
| // URN/urn Universal Resource Name |
| |
| // Forward declaration because of circular reference: |
| // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse |
| // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again |
| mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n); |
| |
| #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (n)->tcpInfo.retries) |
| |
| // Note that this function assumes src is already NULL terminated |
| mDNSlocal void AllocAndCopy(mDNSu8** dst, mDNSu8* src) |
| { |
| if (src == mDNSNULL) return; |
| if ((*dst = (mDNSu8 *) mDNSPlatformMemAllocate(strlen((char*)src) + 1)) == mDNSNULL) { LogMsg("AllocAndCopy: can't allocate string"); return; } |
| strcpy((char *)*dst, (char*)src); |
| } |
| |
| // This function does a simple parse of an HTTP URL that may include a hostname, port, and path |
| // 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) |
| mDNSlocal mStatus ParseHttpUrl(char* ptr, char* end, mDNSu8** addressAndPort, mDNSIPPort* port, mDNSu8** path) |
| { |
| // if the data begins with "http://", we assume there is a hostname and possibly a port number |
| if (end - ptr >= 7 && strncasecmp(ptr, "http://", 7) == 0) |
| { |
| int i; |
| char* stop = end; |
| char* addrPtr = mDNSNULL; |
| |
| ptr += 7; //skip over "http://" |
| if (ptr >= end) { LogInfo("ParseHttpUrl: past end of buffer parsing host:port"); return mStatus_BadParamErr; } |
| |
| // find the end of the host:port |
| addrPtr = ptr; |
| for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break; |
| |
| // allocate the buffer (len i+1 so we have space to terminate the string) |
| if ((*addressAndPort = (mDNSu8 *) mDNSPlatformMemAllocate(i+1)) == mDNSNULL) { LogMsg("ParseHttpUrl: can't allocate address string"); return mStatus_NoMemoryErr; } |
| strncpy((char *)*addressAndPort, ptr, i); |
| (*addressAndPort)[i] = '\0'; |
| |
| // find the port number in the string, by looking backwards for the ':' |
| stop = ptr; // can't go back farther than the original start |
| ptr = addrPtr; // move ptr to the path part |
| |
| for (addrPtr--;addrPtr>stop;addrPtr--) |
| { |
| if (*addrPtr == ':') |
| { |
| int tmpport; |
| addrPtr++; // skip over ':' |
| tmpport = (int)strtol(addrPtr, mDNSNULL, 10); |
| *port = mDNSOpaque16fromIntVal(tmpport); // store it properly converted |
| break; |
| } |
| } |
| } |
| |
| // ptr should now point to the first character we haven't yet processed |
| // everything that remains is the path |
| if (path && ptr < end) |
| { |
| if ((*path = (mDNSu8 *)mDNSPlatformMemAllocate(end - ptr + 1)) == mDNSNULL) { LogMsg("ParseHttpUrl: can't mDNSPlatformMemAllocate path"); return mStatus_NoMemoryErr; } |
| strncpy((char *)*path, ptr, end - ptr); |
| (*path)[end - ptr] = '\0'; |
| } |
| |
| return mStatus_NoError; |
| } |
| |
| enum |
| { |
| HTTPCode_NeedMoreData = -1, // No code found in stream |
| HTTPCode_Other = -2, // Valid code other than those below found in stream |
| HTTPCode_Bad = -3, |
| HTTPCode_200 = 200, |
| HTTPCode_404 = 404, |
| HTTPCode_500 = 500, |
| }; |
| |
| mDNSlocal mDNSs16 ParseHTTPResponseCode(mDNSu8** data, mDNSu8* end) |
| { |
| mDNSu8* ptr = *data; |
| char * code; |
| |
| if (end - ptr < 5) return HTTPCode_NeedMoreData; |
| if (strncasecmp((char*)ptr, "HTTP/", 5) != 0) return HTTPCode_Bad; |
| ptr += 5; |
| // should we care about the HTTP protocol version? |
| |
| // look for first space, which must come before first LF |
| while (ptr && ptr != end) |
| { |
| if (*ptr == '\n') return HTTPCode_Bad; |
| if (*ptr == ' ') break; |
| ptr++; |
| } |
| if (ptr == end) return HTTPCode_NeedMoreData; |
| ptr++; |
| |
| if (end - ptr < 3) return HTTPCode_NeedMoreData; |
| |
| code = (char*)ptr; |
| ptr += 3; |
| while (ptr && ptr != end) |
| { |
| if (*ptr == '\n') break; |
| ptr++; |
| } |
| if (ptr == end) return HTTPCode_NeedMoreData; |
| *data = ++ptr; |
| |
| if (memcmp(code, "200", 3) == 0) return HTTPCode_200; |
| if (memcmp(code, "404", 3) == 0) return HTTPCode_404; |
| if (memcmp(code, "500", 3) == 0) return HTTPCode_500; |
| |
| LogInfo("ParseHTTPResponseCode found unexpected result code: %c%c%c", code[0], code[1], code[2]); |
| return HTTPCode_Other; |
| } |
| |
| // This function parses the xml body of the device description response from the router. Basically, we look to make sure this is a response |
| // referencing a service we care about (WANIPConnection or WANPPPConnection), look for the "controlURL" header immediately following, and copy the addressing and URL info we need |
| mDNSlocal void handleLNTDeviceDescriptionResponse(tcpLNTInfo *tcpInfo) |
| { |
| mDNS *m = tcpInfo->m; |
| char *ptr = (char *)tcpInfo->Reply; |
| char *end = (char *)tcpInfo->Reply + tcpInfo->nread; |
| char *stop = mDNSNULL; |
| mDNSs16 http_result; |
| |
| if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return; // already have the info we need |
| |
| http_result = ParseHTTPResponseCode((mDNSu8**)&ptr, (mDNSu8*)end); // Note: modifies ptr |
| if (http_result == HTTPCode_404) LNT_ClearState(m); |
| if (http_result != HTTPCode_200) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "noop", "HTTP Result", "HTTP code: %d", http_result); |
| return; |
| } |
| |
| // Always reset our flag to use WANIPConnection. We'll use WANPPPConnection if we find it and don't find WANIPConnection. |
| m->UPnPWANPPPConnection = mDNSfalse; |
| |
| // find either service we care about |
| while (ptr && ptr < end) |
| { |
| if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break; |
| ptr++; |
| } |
| if (ptr == end) |
| { |
| ptr = (char *)tcpInfo->Reply; |
| while (ptr && ptr < end) |
| { |
| if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0)) |
| { |
| m->UPnPWANPPPConnection = mDNStrue; |
| break; |
| } |
| ptr++; |
| } |
| } |
| if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; } |
| |
| // find "controlURL", starting from where we left off |
| while (ptr && ptr < end) |
| { |
| if (*ptr == 'c' && (strncasecmp(ptr, "controlURL", 10) == 0)) break; // find the first 'c'; is this controlURL? if not, keep looking |
| ptr++; |
| } |
| if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; } |
| ptr += 11; // skip over "controlURL>" |
| 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 |
| |
| // find the end of the controlURL element |
| for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } } |
| |
| // fill in default port |
| m->UPnPSOAPPort = m->UPnPRouterPort; |
| |
| // free string pointers and set to NULL |
| if (m->UPnPSOAPAddressString != mDNSNULL) |
| { |
| mDNSPlatformMemFree(m->UPnPSOAPAddressString); |
| m->UPnPSOAPAddressString = mDNSNULL; |
| } |
| if (m->UPnPSOAPURL != mDNSNULL) |
| { |
| mDNSPlatformMemFree(m->UPnPSOAPURL); |
| m->UPnPSOAPURL = mDNSNULL; |
| } |
| |
| if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, &m->UPnPSOAPURL) != mStatus_NoError) return; |
| // the SOAPURL should look something like "/uuid:0013-108c-4b3f0000f3dc" |
| |
| if (m->UPnPSOAPAddressString == mDNSNULL) |
| { |
| ptr = (char *)tcpInfo->Reply; |
| while (ptr && ptr < end) |
| { |
| if (*ptr == 'U' && (strncasecmp(ptr, "URLBase", 7) == 0)) break; |
| ptr++; |
| } |
| |
| if (ptr < end) // found URLBase |
| { |
| LogInfo("handleLNTDeviceDescriptionResponse: found URLBase"); |
| ptr += 8; // skip over "URLBase>" |
| // find the end of the URLBase element |
| for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } } |
| if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, mDNSNULL) != mStatus_NoError) |
| { |
| LogInfo("handleLNTDeviceDescriptionResponse: failed to parse URLBase"); |
| } |
| } |
| |
| // if all else fails, use the router address string |
| if (m->UPnPSOAPAddressString == mDNSNULL) AllocAndCopy(&m->UPnPSOAPAddressString, m->UPnPRouterAddressString); |
| } |
| if (m->UPnPSOAPAddressString == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPAddressString is NULL"); |
| else LogInfo("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m->UPnPSOAPAddressString); |
| |
| if (m->UPnPSOAPURL == mDNSNULL) AllocAndCopy(&m->UPnPSOAPURL, m->UPnPRouterURL); |
| if (m->UPnPSOAPURL == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPURL is NULL"); |
| else LogInfo("handleLNTDeviceDescriptionResponse: SOAP URL [%s]", m->UPnPSOAPURL); |
| } |
| |
| mDNSlocal void handleLNTGetExternalAddressResponse(tcpLNTInfo *tcpInfo) |
| { |
| mDNS *m = tcpInfo->m; |
| mDNSu16 err = NATErr_None; |
| mDNSv4Addr ExtAddr; |
| mDNSu8 *ptr = (mDNSu8*)tcpInfo->Reply; |
| mDNSu8 *end = (mDNSu8*)tcpInfo->Reply + tcpInfo->nread; |
| mDNSu8 *addrend; |
| static char tagname[20] = "NewExternalIPAddress"; // Array NOT including a terminating nul |
| |
| // LogInfo("handleLNTGetExternalAddressResponse: %s", ptr); |
| |
| mDNSs16 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr |
| if (http_result == HTTPCode_404) LNT_ClearState(m); |
| if (http_result != HTTPCode_200) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "HTTP Result", "HTTP code: %d", http_result); |
| return; |
| } |
| |
| |
| while (ptr < end && strncasecmp((char*)ptr, tagname, sizeof(tagname))) ptr++; |
| ptr += sizeof(tagname); // Skip over "NewExternalIPAddress" |
| while (ptr < end && *ptr != '>') ptr++; |
| ptr += 1; // Skip over ">" |
| // Find the end of the address and terminate the string so inet_pton() can convert it |
| addrend = ptr; |
| while (addrend < end && (mDNSIsDigit(*addrend) || *addrend == '.')) addrend++; |
| if (addrend >= end) return; |
| *addrend = 0; |
| |
| if (inet_pton(AF_INET, (char*)ptr, &ExtAddr) <= 0) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "inet_pton", ""); |
| LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr); |
| err = NATErr_NetFail; |
| ExtAddr = zerov4Addr; |
| } |
| if (!err) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr); |
| |
| natTraversalHandleAddressReply(m, err, ExtAddr); |
| } |
| |
| mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo) |
| { |
| mDNS *m = tcpInfo->m; |
| mDNSIPPort extport = zeroIPPort; |
| mDNSu8 *ptr = (mDNSu8*)tcpInfo->Reply; |
| mDNSu8 *end = (mDNSu8*)tcpInfo->Reply + tcpInfo->nread; |
| NATTraversalInfo *natInfo; |
| mDNSs16 http_result; |
| |
| for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break; } |
| |
| if (!natInfo) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; } |
| |
| http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr |
| if (http_result == HTTPCode_200) |
| { |
| LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)", |
| mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries); |
| |
| // Make sure to compute extport *before* we zero tcpInfo->retries |
| extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo)); |
| tcpInfo->retries = 0; |
| natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE); |
| } |
| else if (http_result == HTTPCode_500) |
| { |
| while (ptr && ptr != end) |
| { |
| if (((*ptr == 'c' || *ptr == 'C') && end - ptr >= 8 && strncasecmp((char*)ptr, "Conflict", 8) == 0) || (*ptr == '>' && end - ptr >= 15 && strncasecmp((char*)ptr, ">718</errorCode", 15) == 0)) |
| { |
| if (tcpInfo->retries < 100) |
| { |
| tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo); |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict", "Retry %d", tcpInfo->retries); |
| } |
| else |
| { |
| LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort)); |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict - too many retries", "Retries: %d", tcpInfo->retries); |
| natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0); |
| } |
| return; |
| } |
| ptr++; |
| } |
| } |
| else if (http_result == HTTPCode_Bad) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response"); |
| else if (http_result == HTTPCode_Other) LogMsg("handleLNTPortMappingResponse got unexpected response code"); |
| else if (http_result == HTTPCode_404) LNT_ClearState(m); |
| if (http_result != HTTPCode_200 && http_result != HTTPCode_500) |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "HTTP Result", "HTTP code: %d", http_result); |
| } |
| |
| mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo) |
| { |
| tcpLNTInfo **ptr = &m->tcpInfoUnmapList; |
| while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next; |
| if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); } // If we found it, cut it from our list and free the memory |
| } |
| |
| mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err) |
| { |
| mStatus status = mStatus_NoError; |
| tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context; |
| mDNSBool closed = mDNSfalse; |
| long n = 0; |
| long nsent = 0; |
| |
| if (tcpInfo == mDNSNULL) { LogInfo("tcpConnectionCallback: no tcpInfo context"); status = mStatus_Invalid; goto exit; } |
| |
| // The handlers below expect to be called with the lock held |
| mDNS_Lock(tcpInfo->m); |
| |
| if (err) { LogInfo("tcpConnectionCallback: received error"); goto exit; } |
| |
| if (ConnectionEstablished) // connection is established - send the message |
| { |
| LogInfo("tcpConnectionCallback: connection established, sending message"); |
| nsent = mDNSPlatformWriteTCP(sock, (char *)tcpInfo->Request, tcpInfo->requestLen); |
| if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; } |
| } |
| else |
| { |
| n = mDNSPlatformReadTCP(sock, (char *)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed); |
| LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n); |
| |
| if (n < 0) { LogInfo("tcpConnectionCallback - read returned %d", n); status = mStatus_ConnFailed; goto exit; } |
| else if (closed) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; } |
| |
| tcpInfo->nread += n; |
| LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread); |
| if (tcpInfo->nread > LNT_MAXBUFSIZE) |
| { |
| LogInfo("result truncated..."); |
| tcpInfo->nread = LNT_MAXBUFSIZE; |
| } |
| |
| switch (tcpInfo->op) |
| { |
| case LNTDiscoveryOp: handleLNTDeviceDescriptionResponse (tcpInfo); break; |
| case LNTExternalAddrOp: handleLNTGetExternalAddressResponse(tcpInfo); break; |
| case LNTPortMapOp: handleLNTPortMappingResponse (tcpInfo); break; |
| case LNTPortMapDeleteOp: status = mStatus_ConfigChanged; break; |
| default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break; |
| } |
| } |
| exit: |
| if (err || status) |
| { |
| mDNS *m = tcpInfo->m; |
| switch (tcpInfo->op) |
| { |
| case LNTDiscoveryOp: if (m->UPnPSOAPAddressString == mDNSNULL) |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP Address", ""); |
| if (m->UPnPSOAPURL == mDNSNULL) |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP path", ""); |
| if (m->UPnPSOAPAddressString && m->UPnPSOAPURL) |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "success", "success", ""); |
| break; |
| case LNTExternalAddrOp: mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", mDNSIPv4AddressIsZero(m->ExternalAddress) ? "failure" : "success", mDNSIPv4AddressIsZero(m->ExternalAddress) ? "failure" : "success", ""); |
| break; |
| case LNTPortMapOp: if (tcpInfo->parentNATInfo) |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", (tcpInfo->parentNATInfo->Result) ? "failure" : "success", |
| (tcpInfo->parentNATInfo->Result) ? "failure" : "success", "Result: %d", tcpInfo->parentNATInfo->Result); |
| break; |
| case LNTPortMapDeleteOp: break; |
| default: break; |
| } |
| |
| mDNSPlatformTCPCloseConnection(tcpInfo->sock); |
| tcpInfo->sock = mDNSNULL; |
| if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; } |
| if (tcpInfo->Reply ) { mDNSPlatformMemFree(tcpInfo->Reply); tcpInfo->Reply = mDNSNULL; } |
| } |
| |
| if (tcpInfo) mDNS_Unlock(tcpInfo->m); |
| |
| if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo); |
| } |
| |
| mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op) |
| { |
| mStatus err = mStatus_NoError; |
| mDNSIPPort srcport = zeroIPPort; |
| |
| if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port)) |
| { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); } |
| info->m = m; |
| info->Address = *Addr; |
| info->Port = Port; |
| info->op = op; |
| info->nread = 0; |
| info->replyLen = LNT_MAXBUFSIZE; |
| if (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE); // reuse previously allocated buffer |
| else if ((info->Reply = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr); } |
| |
| if (info->sock) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; } |
| info->sock = mDNSPlatformTCPSocket(m, kTCPSocketFlags_Zero, &srcport); |
| if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); } |
| LogInfo("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port)); |
| err = mDNSPlatformTCPConnect(info->sock, Addr, Port, 0, tcpConnectionCallback, info); |
| |
| if (err == mStatus_ConnPending) err = mStatus_NoError; |
| else if (err == mStatus_ConnEstablished) |
| { |
| mDNS_DropLockBeforeCallback(); |
| tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError); |
| mDNS_ReclaimLockAfterCallback(); |
| err = mStatus_NoError; |
| } |
| else |
| { |
| // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc. |
| LogInfo("LNT MakeTCPConnection: connection failed"); |
| mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above |
| info->sock = mDNSNULL; |
| mDNSPlatformMemFree(info->Reply); |
| info->Reply = mDNSNULL; |
| } |
| return(err); |
| } |
| |
| mDNSlocal unsigned int AddSOAPArguments(char *buf, unsigned int maxlen, int numArgs, Property *a) |
| { |
| static const char f1[] = "<%s>%s</%s>"; |
| static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>"; |
| int i, len = 0; |
| *buf = 0; |
| for (i = 0; i < numArgs; i++) |
| { |
| if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name); |
| else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name, a[i].value, a[i].name); |
| } |
| return(len); |
| } |
| |
| mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, char *Action, int numArgs, Property *Arguments, LNTOp_t op) |
| { |
| // SOAP message header format - |
| // - control URL |
| // - action (string) |
| // - router's host/port ("host:port") |
| // - content-length |
| static const char header[] = |
| "POST %s HTTP/1.1\r\n" |
| "Content-Type: text/xml; charset=\"utf-8\"\r\n" |
| "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n" |
| "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n" |
| "Host: %s\r\n" |
| "Content-Length: %d\r\n" |
| "Connection: close\r\n" |
| "Pragma: no-cache\r\n" |
| "\r\n" |
| "%s\r\n"; |
| |
| static const char body1[] = |
| "<?xml version=\"1.0\"?>\r\n" |
| "<SOAP-ENV:Envelope" |
| " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"" |
| " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" |
| "<SOAP-ENV:Body>" |
| "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">"; |
| |
| static const char body2[] = |
| "</m:%s>" |
| "</SOAP-ENV:Body>" |
| "</SOAP-ENV:Envelope>\r\n"; |
| |
| mStatus err; |
| char *body = (char *)&m->omsg; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty |
| int bodyLen; |
| |
| if (mDNSIPPortIsZero(m->UPnPSOAPPort) || m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL) // if no SOAP URL or address exists get out here |
| { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid; } |
| |
| // Create body |
| bodyLen = mDNS_snprintf (body, sizeof(m->omsg), body1, Action, m->UPnPWANPPPConnection ? "PPP" : "IP"); |
| bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments); |
| bodyLen += mDNS_snprintf (body + bodyLen, sizeof(m->omsg) - bodyLen, body2, Action); |
| |
| // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field |
| if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE); |
| if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; } |
| info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body); |
| |
| err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op); |
| if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; } |
| return err; |
| } |
| |
| // Build port mapping request with new port (up to max) and send it |
| mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n) |
| { |
| char externalPort[6]; |
| char internalPort[6]; |
| char localIPAddrString[30]; |
| char publicPortString[40]; |
| Property propArgs[8]; |
| mDNSu16 ReqPortNum = RequestedPortNum(n); |
| NATTraversalInfo *n2 = m->NATTraversals; |
| |
| // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique. |
| // UPnP gateways will report conflicts if different devices request the same external port, but if two |
| // clients on the same device request the same external port the second one just stomps over the first. |
| // One way this can happen is like this: |
| // 1. Client A binds local port 80 |
| // 2. Client A requests external port 80 -> internal port 80 |
| // 3. UPnP NAT gateway refuses external port 80 (some other client already has it) |
| // 4. Client A tries again, and successfully gets external port 80 -> internal port 81 |
| // 5. Client B on same machine tries to bind local port 80, and fails |
| // 6. Client B tries again, and successfully binds local port 81 |
| // 7. Client B now requests external port 81 -> internal port 81 |
| // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping |
| |
| while (n2) |
| { |
| if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next; |
| else |
| { |
| if (n->tcpInfo.retries < 100) |
| { |
| n->tcpInfo.retries++; |
| ReqPortNum = RequestedPortNum(n); // Pick a new port number |
| n2 = m->NATTraversals; // And re-scan the list looking for conflicts |
| } |
| else |
| { |
| natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0); |
| return mStatus_NoError; |
| } |
| } |
| } |
| |
| // create strings to use in the message |
| mDNS_snprintf(externalPort, sizeof(externalPort), "%u", ReqPortNum); |
| mDNS_snprintf(internalPort, sizeof(internalPort), "%u", mDNSVal16(n->IntPort)); |
| mDNS_snprintf(publicPortString, sizeof(publicPortString), "iC%u", ReqPortNum); |
| mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u", |
| m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]); |
| |
| // build the message |
| mDNSPlatformMemZero(propArgs, sizeof(propArgs)); |
| propArgs[0].name = "NewRemoteHost"; |
| propArgs[0].type = "string"; |
| propArgs[0].value = ""; |
| propArgs[1].name = "NewExternalPort"; |
| propArgs[1].type = "ui2"; |
| propArgs[1].value = externalPort; |
| propArgs[2].name = "NewProtocol"; |
| propArgs[2].type = "string"; |
| propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP"; |
| propArgs[3].name = "NewInternalPort"; |
| propArgs[3].type = "ui2"; |
| propArgs[3].value = internalPort; |
| propArgs[4].name = "NewInternalClient"; |
| propArgs[4].type = "string"; |
| propArgs[4].value = localIPAddrString; |
| propArgs[5].name = "NewEnabled"; |
| propArgs[5].type = "boolean"; |
| propArgs[5].value = "1"; |
| propArgs[6].name = "NewPortMappingDescription"; |
| propArgs[6].type = "string"; |
| propArgs[6].value = publicPortString; |
| propArgs[7].name = "NewLeaseDuration"; |
| propArgs[7].type = "ui4"; |
| propArgs[7].value = "0"; |
| |
| LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum); |
| return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp); |
| } |
| |
| mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *n) |
| { |
| LogInfo("LNT_MapPort"); |
| if (n->tcpInfo.sock) return(mStatus_NoError); // If we already have a connection up don't make another request for the same thing |
| n->tcpInfo.parentNATInfo = n; |
| n->tcpInfo.retries = 0; |
| return SendPortMapRequest(m, n); |
| } |
| |
| mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *n) |
| { |
| char externalPort[10]; |
| Property propArgs[3]; |
| tcpLNTInfo *info; |
| tcpLNTInfo **infoPtr = &m->tcpInfoUnmapList; |
| mStatus err; |
| |
| // If no NAT gateway to talk to, no need to do all this work for nothing |
| if (mDNSIPPortIsZero(m->UPnPSOAPPort) || !m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError; |
| |
| mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort)); |
| |
| mDNSPlatformMemZero(propArgs, sizeof(propArgs)); |
| propArgs[0].name = "NewRemoteHost"; |
| propArgs[0].type = "string"; |
| propArgs[0].value = ""; |
| propArgs[1].name = "NewExternalPort"; |
| propArgs[1].type = "ui2"; |
| propArgs[1].value = externalPort; |
| propArgs[2].name = "NewProtocol"; |
| propArgs[2].type = "string"; |
| propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP"; |
| |
| n->tcpInfo.parentNATInfo = n; |
| |
| // clean up previous port mapping requests and allocations |
| if (n->tcpInfo.sock) LogInfo("LNT_UnmapPort: closing previous open connection"); |
| if (n->tcpInfo.sock ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock = mDNSNULL; } |
| if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request); n->tcpInfo.Request = mDNSNULL; } |
| if (n->tcpInfo.Reply ) { mDNSPlatformMemFree(n->tcpInfo.Reply); n->tcpInfo.Reply = mDNSNULL; } |
| |
| // 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) |
| if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL) |
| { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); } |
| *info = n->tcpInfo; |
| |
| while (*infoPtr) infoPtr = &(*infoPtr)->next; // find the end of the list |
| *infoPtr = info; // append |
| |
| err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp); |
| if (err) DisposeInfoFromUnmapList(m, info); |
| return err; |
| } |
| |
| mDNSexport mStatus LNT_GetExternalAddress(mDNS *m) |
| { |
| return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp); |
| } |
| |
| mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info) |
| { |
| // Device description format - |
| // - device description URL |
| // - host/port |
| static const char szSSDPMsgDescribeDeviceFMT[] = |
| "GET %s HTTP/1.1\r\n" |
| "Accept: text/xml, application/xml\r\n" |
| "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n" |
| "Host: %s\r\n" |
| "Connection: close\r\n" |
| "\r\n"; |
| |
| if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return mStatus_NoError; // already have the info we need |
| |
| if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); } |
| |
| // build message |
| if (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer |
| else if ((info->Request = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); } |
| info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString); |
| LogInfo("Describe Device: [%s]", info->Request); |
| return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp); |
| } |
| |
| // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response |
| // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and |
| // URL info we need. |
| mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, mDNSu8 *data, mDNSu16 len) |
| { |
| char *ptr = (char *)data; |
| char *end = (char *)data + len; |
| char *stop = ptr; |
| |
| if (!mDNSIPPortIsZero(m->UPnPRouterPort)) return; // already have the info we need |
| |
| // The formatting of the HTTP header is not always the same when it comes to the placement of |
| // the service and location strings, so we just look for each of them from the beginning for every response |
| |
| // figure out if this is a message from a service we care about |
| while (ptr && ptr != end) |
| { |
| if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break; |
| ptr++; |
| } |
| if (ptr == end) |
| { |
| ptr = (char *)data; |
| while (ptr && ptr != end) |
| { |
| if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0)) break; |
| ptr++; |
| } |
| } |
| if (ptr == mDNSNULL || ptr == end) return; // not a message we care about |
| |
| // find "Location:", starting from the beginning |
| ptr = (char *)data; |
| while (ptr && ptr != end) |
| { |
| if (*ptr == 'L' && (strncasecmp(ptr, "Location:", 9) == 0)) break; // find the first 'L'; is this Location? if not, keep looking |
| ptr++; |
| } |
| if (ptr == mDNSNULL || ptr == end) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Location", ""); |
| return; // not a message we care about |
| } |
| ptr += 9; //Skip over 'Location:' |
| while (*ptr == ' ' && ptr < end) ptr++; // skip over spaces |
| if (ptr >= end) return; |
| |
| // find the end of the line |
| for (stop = ptr; stop != end; stop++) { if (*stop == '\r') { end = stop; break; } } |
| |
| // fill in default port |
| m->UPnPRouterPort = mDNSOpaque16fromIntVal(80); |
| |
| // free string pointers and set to NULL |
| if (m->UPnPRouterAddressString != mDNSNULL) |
| { |
| mDNSPlatformMemFree(m->UPnPRouterAddressString); |
| m->UPnPRouterAddressString = mDNSNULL; |
| } |
| if (m->UPnPRouterURL != mDNSNULL) |
| { |
| mDNSPlatformMemFree(m->UPnPRouterURL); |
| m->UPnPRouterURL = mDNSNULL; |
| } |
| |
| // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc" |
| if (ParseHttpUrl(ptr, end, &m->UPnPRouterAddressString, &m->UPnPRouterPort, &m->UPnPRouterURL) != mStatus_NoError) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Parse URL", ""); |
| return; |
| } |
| |
| m->UPnPInterfaceID = InterfaceID; |
| |
| if (m->UPnPRouterAddressString == mDNSNULL) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router address", ""); |
| LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL"); |
| } |
| else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m->UPnPRouterAddressString); |
| |
| if (m->UPnPRouterURL == mDNSNULL) |
| { |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router path", ""); |
| LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL"); |
| } |
| else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m->UPnPRouterURL); |
| |
| LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m->UPnPRouterPort)); |
| LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m->UPnPInterfaceID); |
| |
| // Don't need the SSDP socket anymore |
| if (m->SSDPSocket) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; } |
| |
| mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "success", "success", ""); |
| // now send message to get the device description |
| GetDeviceDescription(m, &m->tcpDeviceInfo); |
| } |
| |
| mDNSexport void LNT_SendDiscoveryMsg(mDNS *m) |
| { |
| static const char msg[] = |
| "M-SEARCH * HTTP/1.1\r\n" |
| "Host:239.255.255.250:1900\r\n" |
| "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n" |
| "Man:\"ssdp:discover\"\r\n" |
| "MX:3\r\n\r\n"; |
| static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } }; |
| |
| mDNSu8* buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty |
| unsigned int bufLen; |
| |
| if (!mDNSIPPortIsZero(m->UPnPRouterPort)) |
| { |
| if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; } |
| if (mDNSIPPortIsZero(m->UPnPSOAPPort) && !m->tcpDeviceInfo.sock) GetDeviceDescription(m, &m->tcpDeviceInfo); |
| return; |
| } |
| |
| // Always query for WANIPConnection in the first SSDP packet |
| if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse; |
| |
| // Create message |
| bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP"); |
| |
| debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExternalAddress); |
| |
| if (!mDNSIPv4AddressIsZero(m->Router.ip.v4)) |
| { |
| if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(m, zeroIPPort); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); } |
| mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router, SSDPPort); |
| mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort); |
| } |
| |
| m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection; |
| } |
| |
| mDNSexport void LNT_ClearState(mDNS *const m) |
| { |
| if (m->tcpAddrInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpAddrInfo.sock); m->tcpAddrInfo.sock = mDNSNULL; } |
| if (m->tcpDeviceInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpDeviceInfo.sock); m->tcpDeviceInfo.sock = mDNSNULL; } |
| m->UPnPSOAPPort = m->UPnPRouterPort = zeroIPPort; // Reset UPnP ports |
| } |
| |
| #endif /* _LEGACY_NAT_TRAVERSAL_ */ |