1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2004 Apple Computer, 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()
24 # include <winsock2.h>
25 # include <ws2tcpip.h>
26 # define strcasecmp _stricmp
27 # define strncasecmp _strnicmp
28 # define mDNSASLLog( UUID, SUBDOMAIN, RESULT, SIGNATURE, FORMAT, ... ) ;
31 inet_pton( int family
, const char * addr
, void * dst
)
33 struct sockaddr_storage ss
;
34 int sslen
= sizeof( ss
);
36 ZeroMemory( &ss
, sizeof( ss
) );
37 ss
.ss_family
= (ADDRESS_FAMILY
)family
;
39 if ( WSAStringToAddressA( (LPSTR
)addr
, family
, NULL
, ( struct sockaddr
* ) &ss
, &sslen
) == 0 )
41 if ( family
== AF_INET
) { memcpy( dst
, &( ( struct sockaddr_in
* ) &ss
)->sin_addr
, sizeof( IN_ADDR
) ); return 1; }
42 else if ( family
== AF_INET6
) { memcpy( dst
, &( ( struct sockaddr_in6
* ) &ss
)->sin6_addr
, sizeof( IN6_ADDR
) ); return 1; }
48 # include <arpa/inet.h> // For inet_pton()
51 #include "mDNSEmbeddedAPI.h"
52 #include "uDNS.h" // For natTraversalHandleAddressReply() etc.
54 // used to format SOAP port mapping arguments
55 typedef struct Property_struct
62 // All of the text parsing in this file is intentionally transparent so that we know exactly
63 // what's being done to the text, with an eye towards preventing security problems.
65 // This is an evolving list of useful acronyms to know. Please add to it at will.
67 // NT Notification Type
68 // USN Unique Service Name
69 // UDN Unique Device Name
70 // UUID Universally Unique Identifier
71 // URN/urn Universal Resource Name
73 // Forward declaration because of circular reference:
74 // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse
75 // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again
76 mDNSlocal mStatus
SendPortMapRequest(mDNS
*m
, NATTraversalInfo
*n
);
78 #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (mDNSu16)(n)->tcpInfo.retries)
80 // Note that this function assumes src is already NULL terminated
81 mDNSlocal
void AllocAndCopy(mDNSu8
**const dst
, const mDNSu8
*const src
)
83 if (src
== mDNSNULL
) return;
84 if ((*dst
= mDNSPlatformMemAllocate((mDNSu32
)strlen((char*)src
) + 1)) == mDNSNULL
)
85 { LogMsg("AllocAndCopy: can't allocate string"); return; }
86 strcpy((char*)*dst
, (char*)src
);
89 // This function does a simple parse of an HTTP URL that may include a hostname, port, and path
90 // 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)
91 mDNSlocal mStatus
ParseHttpUrl(const mDNSu8
*ptr
, const mDNSu8
*const end
, mDNSu8
**const addressAndPort
, mDNSIPPort
*const port
, mDNSu8
**const path
)
93 // if the data begins with "http://", we assume there is a hostname and possibly a port number
94 if (end
- ptr
>= 7 && strncasecmp((char*)ptr
, "http://", 7) == 0)
97 const mDNSu8
*stop
= end
;
98 const mDNSu8
*addrPtr
= mDNSNULL
;
100 ptr
+= 7; //skip over "http://"
101 if (ptr
>= end
) { LogInfo("ParseHttpUrl: past end of buffer parsing host:port"); return mStatus_BadParamErr
; }
103 // find the end of the host:port
105 for (i
= 0; addrPtr
&& addrPtr
!= end
; i
++, addrPtr
++) if (*addrPtr
== '/') break;
107 // allocate the buffer (len i+1 so we have space to terminate the string)
108 if ((*addressAndPort
= mDNSPlatformMemAllocate(i
+1)) == mDNSNULL
)
109 { LogMsg("ParseHttpUrl: can't allocate address string"); return mStatus_NoMemoryErr
; }
110 strncpy((char*)*addressAndPort
, (char*)ptr
, i
);
111 (*addressAndPort
)[i
] = '\0';
113 // find the port number in the string, by looking backwards for the ':'
114 stop
= ptr
; // can't go back farther than the original start
115 ptr
= addrPtr
; // move ptr to the path part
117 for (addrPtr
--; addrPtr
>stop
; addrPtr
--)
121 addrPtr
++; // skip over ':'
122 *port
= mDNSOpaque16fromIntVal((mDNSu16
)strtol((char*)addrPtr
, mDNSNULL
, 10)); // store it properly converted
128 // ptr should now point to the first character we haven't yet processed
129 // everything that remains is the path
130 if (path
&& ptr
< end
)
132 if ((*path
= mDNSPlatformMemAllocate((mDNSu32
)(end
- ptr
) + 1)) == mDNSNULL
)
133 { LogMsg("ParseHttpUrl: can't mDNSPlatformMemAllocate path"); return mStatus_NoMemoryErr
; }
134 strncpy((char*)*path
, (char*)ptr
, end
- ptr
);
135 (*path
)[end
- ptr
] = '\0';
138 return mStatus_NoError
;
143 HTTPCode_NeedMoreData
= -1, // No code found in stream
144 HTTPCode_Other
= -2, // Valid code other than those below found in stream
151 mDNSlocal mDNSs16
ParseHTTPResponseCode(const mDNSu8
**const data
, const mDNSu8
*const end
)
153 const mDNSu8
*ptr
= *data
;
156 if (end
- ptr
< 5) return HTTPCode_NeedMoreData
;
157 if (strncasecmp((char*)ptr
, "HTTP/", 5) != 0) return HTTPCode_Bad
;
159 // should we care about the HTTP protocol version?
161 // look for first space, which must come before first LF
162 while (ptr
&& ptr
!= end
)
164 if (*ptr
== '\n') return HTTPCode_Bad
;
165 if (*ptr
== ' ') break;
168 if (ptr
== end
) return HTTPCode_NeedMoreData
;
171 if (end
- ptr
< 3) return HTTPCode_NeedMoreData
;
175 while (ptr
&& ptr
!= end
)
177 if (*ptr
== '\n') break;
180 if (ptr
== end
) return HTTPCode_NeedMoreData
;
183 if (memcmp((char*)code
, "200", 3) == 0) return HTTPCode_200
;
184 if (memcmp((char*)code
, "404", 3) == 0) return HTTPCode_404
;
185 if (memcmp((char*)code
, "500", 3) == 0) return HTTPCode_500
;
187 LogInfo("ParseHTTPResponseCode found unexpected result code: %c%c%c", code
[0], code
[1], code
[2]);
188 return HTTPCode_Other
;
191 // This function parses the xml body of the device description response from the router. Basically, we look to
192 // make sure this is a response referencing a service we care about (WANIPConnection or WANPPPConnection),
193 // look for the "controlURL" header immediately following, and copy the addressing and URL info we need
194 mDNSlocal
void handleLNTDeviceDescriptionResponse(tcpLNTInfo
*tcpInfo
)
196 mDNS
*m
= tcpInfo
->m
;
197 const mDNSu8
*ptr
= tcpInfo
->Reply
;
198 const mDNSu8
*end
= tcpInfo
->Reply
+ tcpInfo
->nread
;
202 if (!mDNSIPPortIsZero(m
->UPnPSOAPPort
)) return; // already have the info we need
204 http_result
= ParseHTTPResponseCode(&ptr
, end
); // Note: modifies ptr
205 if (http_result
== HTTPCode_404
) LNT_ClearState(m
);
206 if (http_result
!= HTTPCode_200
)
208 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.DeviceDescription", "noop", "HTTP Result", "HTTP code: %d", http_result
);
212 // Always reset our flag to use WANIPConnection. We'll use WANPPPConnection if we find it and don't find WANIPConnection.
213 m
->UPnPWANPPPConnection
= mDNSfalse
;
215 // find either service we care about
216 while (ptr
&& ptr
< end
)
218 if ((*ptr
& 0xDF) == 'W' && (strncasecmp((char*)ptr
, "WANIPConnection:1", 17) == 0)) break;
223 ptr
= tcpInfo
->Reply
;
224 while (ptr
&& ptr
< end
)
226 if ((*ptr
& 0xDF) == 'W' && (strncasecmp((char*)ptr
, "WANPPPConnection:1", 18) == 0))
228 m
->UPnPWANPPPConnection
= mDNStrue
;
234 if (ptr
== mDNSNULL
|| ptr
== end
) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; }
236 // find "controlURL", starting from where we left off
237 while (ptr
&& ptr
< end
)
239 if ((*ptr
& 0xDF) == 'C' && (strncasecmp((char*)ptr
, "controlURL", 10) == 0)) break; // find the first 'c'; is this controlURL? if not, keep looking
242 if (ptr
== mDNSNULL
|| ptr
== end
) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; }
243 ptr
+= 11; // skip over "controlURL>"
244 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
246 // find the end of the controlURL element
247 for (stop
= ptr
; stop
< end
; stop
++) { if (*stop
== '<') { end
= stop
; break; } }
249 // fill in default port
250 m
->UPnPSOAPPort
= m
->UPnPRouterPort
;
252 // free string pointers and set to NULL
253 if (m
->UPnPSOAPAddressString
!= mDNSNULL
)
255 mDNSPlatformMemFree(m
->UPnPSOAPAddressString
);
256 m
->UPnPSOAPAddressString
= mDNSNULL
;
258 if (m
->UPnPSOAPURL
!= mDNSNULL
)
260 mDNSPlatformMemFree(m
->UPnPSOAPURL
);
261 m
->UPnPSOAPURL
= mDNSNULL
;
264 if (ParseHttpUrl(ptr
, end
, &m
->UPnPSOAPAddressString
, &m
->UPnPSOAPPort
, &m
->UPnPSOAPURL
) != mStatus_NoError
) return;
265 // the SOAPURL should look something like "/uuid:0013-108c-4b3f0000f3dc"
267 if (m
->UPnPSOAPAddressString
== mDNSNULL
)
269 ptr
= tcpInfo
->Reply
;
270 while (ptr
&& ptr
< end
)
272 if ((*ptr
& 0xDF) == 'U' && (strncasecmp((char*)ptr
, "URLBase", 7) == 0)) break;
276 if (ptr
< end
) // found URLBase
278 LogInfo("handleLNTDeviceDescriptionResponse: found URLBase");
279 ptr
+= 8; // skip over "URLBase>"
280 // find the end of the URLBase element
281 for (stop
= ptr
; stop
< end
; stop
++) { if (*stop
== '<') { end
= stop
; break; } }
282 if (ParseHttpUrl(ptr
, end
, &m
->UPnPSOAPAddressString
, &m
->UPnPSOAPPort
, mDNSNULL
) != mStatus_NoError
)
284 LogInfo("handleLNTDeviceDescriptionResponse: failed to parse URLBase");
288 // if all else fails, use the router address string
289 if (m
->UPnPSOAPAddressString
== mDNSNULL
) AllocAndCopy(&m
->UPnPSOAPAddressString
, m
->UPnPRouterAddressString
);
291 if (m
->UPnPSOAPAddressString
== mDNSNULL
) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPAddressString is NULL");
292 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m
->UPnPSOAPAddressString
);
294 if (m
->UPnPSOAPURL
== mDNSNULL
) AllocAndCopy(&m
->UPnPSOAPURL
, m
->UPnPRouterURL
);
295 if (m
->UPnPSOAPURL
== mDNSNULL
) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPURL is NULL");
296 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP URL [%s]", m
->UPnPSOAPURL
);
299 mDNSlocal
void handleLNTGetExternalAddressResponse(tcpLNTInfo
*tcpInfo
)
301 mDNS
*m
= tcpInfo
->m
;
302 mDNSu16 err
= NATErr_None
;
304 const mDNSu8
*ptr
= tcpInfo
->Reply
;
305 const mDNSu8
*end
= tcpInfo
->Reply
+ tcpInfo
->nread
;
307 static char tagname
[20] = { 'N','e','w','E','x','t','e','r','n','a','l','I','P','A','d','d','r','e','s','s' };
308 // Array NOT including a terminating nul
310 // LogInfo("handleLNTGetExternalAddressResponse: %s", ptr);
312 mDNSs16 http_result
= ParseHTTPResponseCode(&ptr
, end
); // Note: modifies ptr
313 if (http_result
== HTTPCode_404
) LNT_ClearState(m
);
314 if (http_result
!= HTTPCode_200
)
316 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.AddressRequest", "noop", "HTTP Result", "HTTP code: %d", http_result
);
320 while (ptr
< end
&& strncasecmp((char*)ptr
, tagname
, sizeof(tagname
))) ptr
++;
321 ptr
+= sizeof(tagname
); // Skip over "NewExternalIPAddress"
322 while (ptr
< end
&& *ptr
!= '>') ptr
++;
323 ptr
+= 1; // Skip over ">"
325 // Find the end of the address and terminate the string so inet_pton() can convert it
326 // (Might be better to copy this to a local string here -- this is overwriting tcpInfo->Reply in-place
327 addrend
= (mDNSu8
*)ptr
;
328 while (addrend
< end
&& (mDNSIsDigit(*addrend
) || *addrend
== '.')) addrend
++;
329 if (addrend
>= end
) return;
332 if (inet_pton(AF_INET
, (char*)ptr
, &ExtAddr
) <= 0)
334 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.AddressRequest", "noop", "inet_pton", "");
335 LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr
);
336 err
= NATErr_NetFail
;
337 ExtAddr
= zerov4Addr
;
339 if (!err
) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr
);
341 natTraversalHandleAddressReply(m
, err
, ExtAddr
);
344 mDNSlocal
void handleLNTPortMappingResponse(tcpLNTInfo
*tcpInfo
)
346 mDNS
*m
= tcpInfo
->m
;
347 mDNSIPPort extport
= zeroIPPort
;
348 const mDNSu8
*ptr
= tcpInfo
->Reply
;
349 const mDNSu8
*const end
= tcpInfo
->Reply
+ tcpInfo
->nread
;
350 NATTraversalInfo
*natInfo
;
353 for (natInfo
= m
->NATTraversals
; natInfo
; natInfo
=natInfo
->next
) { if (natInfo
== tcpInfo
->parentNATInfo
) break; }
355 if (!natInfo
) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; }
357 http_result
= ParseHTTPResponseCode(&ptr
, end
); // Note: modifies ptr
358 if (http_result
== HTTPCode_200
)
360 LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)",
361 mDNSVal16(natInfo
->IntPort
), RequestedPortNum(natInfo
), tcpInfo
->retries
);
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
);
368 else if (http_result
== HTTPCode_500
)
370 while (ptr
&& ptr
!= end
)
372 if (((*ptr
& 0xDF) == 'C' && end
- ptr
>= 8 && strncasecmp((char*)ptr
, "Conflict", 8) == 0) ||
373 (*ptr
== '>' && end
- ptr
>= 15 && strncasecmp((char*)ptr
, ">718</errorCode", 15) == 0))
375 if (tcpInfo
->retries
< 100)
377 tcpInfo
->retries
++; SendPortMapRequest(tcpInfo
->m
, natInfo
);
378 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.PortMapRequest", "noop", "Conflict", "Retry %d", tcpInfo
->retries
);
382 LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo
->IntPort
), mDNSVal16(natInfo
->RequestedPort
));
383 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.PortMapRequest", "noop", "Conflict - too many retries", "Retries: %d", tcpInfo
->retries
);
384 natTraversalHandlePortMapReply(m
, natInfo
, m
->UPnPInterfaceID
, NATErr_Res
, zeroIPPort
, 0);
391 else if (http_result
== HTTPCode_Bad
) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response");
392 else if (http_result
== HTTPCode_Other
) LogMsg("handleLNTPortMappingResponse got unexpected response code");
393 else if (http_result
== HTTPCode_404
) LNT_ClearState(m
);
394 if (http_result
!= HTTPCode_200
&& http_result
!= HTTPCode_500
)
395 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.PortMapRequest", "noop", "HTTP Result", "HTTP code: %d", http_result
);
398 mDNSlocal
void DisposeInfoFromUnmapList(mDNS
*m
, tcpLNTInfo
*tcpInfo
)
400 tcpLNTInfo
**ptr
= &m
->tcpInfoUnmapList
;
401 while (*ptr
&& *ptr
!= tcpInfo
) ptr
= &(*ptr
)->next
;
402 if (*ptr
) { *ptr
= (*ptr
)->next
; mDNSPlatformMemFree(tcpInfo
); } // If we found it, cut it from our list and free the memory
405 mDNSlocal
void tcpConnectionCallback(TCPSocket
*sock
, void *context
, mDNSBool ConnectionEstablished
, mStatus err
)
407 mStatus status
= mStatus_NoError
;
408 tcpLNTInfo
*tcpInfo
= (tcpLNTInfo
*)context
;
409 mDNSBool closed
= mDNSfalse
;
413 if (tcpInfo
== mDNSNULL
) { LogInfo("tcpConnectionCallback: no tcpInfo context"); status
= mStatus_Invalid
; goto exit
; }
415 // The handlers below expect to be called with the lock held
416 mDNS_Lock(tcpInfo
->m
);
418 if (err
) { LogInfo("tcpConnectionCallback: received error"); goto exit
; }
420 if (ConnectionEstablished
) // connection is established - send the message
422 LogInfo("tcpConnectionCallback: connection established, sending message");
423 nsent
= mDNSPlatformWriteTCP(sock
, (char*)tcpInfo
->Request
, tcpInfo
->requestLen
);
424 if (nsent
!= (long)tcpInfo
->requestLen
) { LogMsg("tcpConnectionCallback: error writing"); status
= mStatus_UnknownErr
; goto exit
; }
428 n
= mDNSPlatformReadTCP(sock
, (char*)tcpInfo
->Reply
+ tcpInfo
->nread
, tcpInfo
->replyLen
- tcpInfo
->nread
, &closed
);
429 LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n
);
431 if (n
< 0) { LogInfo("tcpConnectionCallback - read returned %d", n
); status
= mStatus_ConnFailed
; goto exit
; }
432 else if (closed
) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo
->nread
); status
= mStatus_ConnFailed
; goto exit
; }
435 LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo
->nread
);
436 if (tcpInfo
->nread
> LNT_MAXBUFSIZE
)
438 LogInfo("result truncated...");
439 tcpInfo
->nread
= LNT_MAXBUFSIZE
;
444 case LNTDiscoveryOp
: handleLNTDeviceDescriptionResponse (tcpInfo
); break;
445 case LNTExternalAddrOp
: handleLNTGetExternalAddressResponse(tcpInfo
); break;
446 case LNTPortMapOp
: handleLNTPortMappingResponse (tcpInfo
); break;
447 case LNTPortMapDeleteOp
: status
= mStatus_ConfigChanged
; break;
448 default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo
->op
); status
= mStatus_Invalid
; break;
454 mDNS
*m
= tcpInfo
->m
;
457 case LNTDiscoveryOp
: if (m
->UPnPSOAPAddressString
== mDNSNULL
)
458 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.DeviceDescription", "failure", "SOAP Address", "");
459 if (m
->UPnPSOAPURL
== mDNSNULL
)
460 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.DeviceDescription", "failure", "SOAP path", "");
461 if (m
->UPnPSOAPAddressString
&& m
->UPnPSOAPURL
)
462 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.DeviceDescription", "success", "success", "");
464 case LNTExternalAddrOp
: mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.AddressRequest",
465 mDNSIPv4AddressIsZero(m
->ExternalAddress
) ? "failure" : "success",
466 mDNSIPv4AddressIsZero(m
->ExternalAddress
) ? "failure" : "success", "");
468 case LNTPortMapOp
: if (tcpInfo
->parentNATInfo
)
469 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.PortMapRequest", (tcpInfo
->parentNATInfo
->Result
) ? "failure" : "success",
470 (tcpInfo
->parentNATInfo
->Result
) ? "failure" : "success", "Result: %d", tcpInfo
->parentNATInfo
->Result
);
472 case LNTPortMapDeleteOp
: break;
476 mDNSPlatformTCPCloseConnection(tcpInfo
->sock
);
477 tcpInfo
->sock
= mDNSNULL
;
478 if (tcpInfo
->Request
) { mDNSPlatformMemFree(tcpInfo
->Request
); tcpInfo
->Request
= mDNSNULL
; }
479 if (tcpInfo
->Reply
) { mDNSPlatformMemFree(tcpInfo
->Reply
); tcpInfo
->Reply
= mDNSNULL
; }
482 if (tcpInfo
) mDNS_Unlock(tcpInfo
->m
);
484 if (status
== mStatus_ConfigChanged
) DisposeInfoFromUnmapList(tcpInfo
->m
, tcpInfo
);
487 mDNSlocal mStatus
MakeTCPConnection(mDNS
*const m
, tcpLNTInfo
*info
, const mDNSAddr
*const Addr
, const mDNSIPPort Port
, LNTOp_t op
)
489 mStatus err
= mStatus_NoError
;
490 mDNSIPPort srcport
= zeroIPPort
;
492 if (mDNSIPv4AddressIsZero(Addr
->ip
.v4
) || mDNSIPPortIsZero(Port
))
493 { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr
, mDNSVal16(Port
)); return(mStatus_Invalid
); }
495 info
->Address
= *Addr
;
499 info
->replyLen
= LNT_MAXBUFSIZE
;
500 if (info
->Reply
!= mDNSNULL
) mDNSPlatformMemZero(info
->Reply
, LNT_MAXBUFSIZE
); // reuse previously allocated buffer
501 else if ((info
->Reply
= mDNSPlatformMemAllocate(LNT_MAXBUFSIZE
)) == mDNSNULL
) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr
); }
503 if (info
->sock
) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info
->sock
); info
->sock
= mDNSNULL
; }
504 info
->sock
= mDNSPlatformTCPSocket(m
, kTCPSocketFlags_Zero
, &srcport
);
505 if (!info
->sock
) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info
->Reply
); info
->Reply
= mDNSNULL
; return(mStatus_NoMemoryErr
); }
506 LogInfo("MakeTCPConnection: connecting to %#a:%d", &info
->Address
, mDNSVal16(info
->Port
));
507 err
= mDNSPlatformTCPConnect(info
->sock
, Addr
, Port
, mDNSNULL
, 0, tcpConnectionCallback
, info
);
509 if (err
== mStatus_ConnPending
) err
= mStatus_NoError
;
510 else if (err
== mStatus_ConnEstablished
)
512 mDNS_DropLockBeforeCallback();
513 tcpConnectionCallback(info
->sock
, info
, mDNStrue
, mStatus_NoError
);
514 mDNS_ReclaimLockAfterCallback();
515 err
= mStatus_NoError
;
519 // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc.
520 LogInfo("LNT MakeTCPConnection: connection failed");
521 mDNSPlatformTCPCloseConnection(info
->sock
); // Dispose the socket we created with mDNSPlatformTCPSocket() above
522 info
->sock
= mDNSNULL
;
523 mDNSPlatformMemFree(info
->Reply
);
524 info
->Reply
= mDNSNULL
;
529 mDNSlocal
unsigned int AddSOAPArguments(char *const buf
, const unsigned int maxlen
, const int numArgs
, const Property
*const a
)
531 static const char f1
[] = "<%s>%s</%s>";
532 static const char f2
[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>";
535 for (i
= 0; i
< numArgs
; i
++)
537 if (a
[i
].type
) len
+= mDNS_snprintf(buf
+ len
, maxlen
- len
, f2
, a
[i
].name
, a
[i
].type
, a
[i
].value
, a
[i
].name
);
538 else len
+= mDNS_snprintf(buf
+ len
, maxlen
- len
, f1
, a
[i
].name
, a
[i
].value
, a
[i
].name
);
543 mDNSlocal mStatus
SendSOAPMsgControlAction(mDNS
*m
, tcpLNTInfo
*info
, const char *const Action
, const int numArgs
, const Property
*const Arguments
, const LNTOp_t op
)
545 // SOAP message header format -
548 // - router's host/port ("host:port")
550 static const char header
[] =
551 "POST %s HTTP/1.1\r\n"
552 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
553 "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n"
554 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n"
556 "Content-Length: %d\r\n"
557 "Connection: close\r\n"
558 "Pragma: no-cache\r\n"
562 static const char body1
[] =
563 "<?xml version=\"1.0\"?>\r\n"
565 " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
566 " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
568 "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">";
570 static const char body2
[] =
573 "</SOAP-ENV:Envelope>\r\n";
576 char *body
= (char*)&m
->omsg
; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty
579 if (mDNSIPPortIsZero(m
->UPnPSOAPPort
) || m
->UPnPSOAPURL
== mDNSNULL
|| m
->UPnPSOAPAddressString
== mDNSNULL
) // if no SOAP URL or address exists get out here
580 { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid
; }
583 bodyLen
= mDNS_snprintf (body
, sizeof(m
->omsg
), body1
, Action
, m
->UPnPWANPPPConnection
? "PPP" : "IP");
584 bodyLen
+= AddSOAPArguments(body
+ bodyLen
, sizeof(m
->omsg
) - bodyLen
, numArgs
, Arguments
);
585 bodyLen
+= mDNS_snprintf (body
+ bodyLen
, sizeof(m
->omsg
) - bodyLen
, body2
, Action
);
587 // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field
588 if (!info
->Request
) info
->Request
= mDNSPlatformMemAllocate(LNT_MAXBUFSIZE
);
589 if (!info
->Request
) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr
; }
590 info
->requestLen
= mDNS_snprintf((char*)info
->Request
, LNT_MAXBUFSIZE
, header
, m
->UPnPSOAPURL
, m
->UPnPWANPPPConnection
? "PPP" : "IP", Action
, m
->UPnPSOAPAddressString
, bodyLen
, body
);
592 err
= MakeTCPConnection(m
, info
, &m
->Router
, m
->UPnPSOAPPort
, op
);
593 if (err
) { mDNSPlatformMemFree(info
->Request
); info
->Request
= mDNSNULL
; }
597 // Build port mapping request with new port (up to max) and send it
598 mDNSlocal mStatus
SendPortMapRequest(mDNS
*m
, NATTraversalInfo
*n
)
600 char externalPort
[6];
601 char internalPort
[6];
602 char localIPAddrString
[30];
603 char publicPortString
[40];
604 Property propArgs
[8];
605 mDNSu16 ReqPortNum
= RequestedPortNum(n
);
606 NATTraversalInfo
*n2
= m
->NATTraversals
;
608 // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique.
609 // UPnP gateways will report conflicts if different devices request the same external port, but if two
610 // clients on the same device request the same external port the second one just stomps over the first.
611 // One way this can happen is like this:
612 // 1. Client A binds local port 80
613 // 2. Client A requests external port 80 -> internal port 80
614 // 3. UPnP NAT gateway refuses external port 80 (some other client already has it)
615 // 4. Client A tries again, and successfully gets external port 80 -> internal port 81
616 // 5. Client B on same machine tries to bind local port 80, and fails
617 // 6. Client B tries again, and successfully binds local port 81
618 // 7. Client B now requests external port 81 -> internal port 81
619 // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping
623 if (n2
== n
|| RequestedPortNum(n2
) != ReqPortNum
) n2
=n2
->next
;
626 if (n
->tcpInfo
.retries
< 100)
628 n
->tcpInfo
.retries
++;
629 ReqPortNum
= RequestedPortNum(n
); // Pick a new port number
630 n2
= m
->NATTraversals
; // And re-scan the list looking for conflicts
634 natTraversalHandlePortMapReply(m
, n
, m
->UPnPInterfaceID
, NATErr_Res
, zeroIPPort
, 0);
635 return mStatus_NoError
;
640 // create strings to use in the message
641 mDNS_snprintf(externalPort
, sizeof(externalPort
), "%u", ReqPortNum
);
642 mDNS_snprintf(internalPort
, sizeof(internalPort
), "%u", mDNSVal16(n
->IntPort
));
643 mDNS_snprintf(publicPortString
, sizeof(publicPortString
), "iC%u", ReqPortNum
);
644 mDNS_snprintf(localIPAddrString
, sizeof(localIPAddrString
), "%u.%u.%u.%u",
645 m
->AdvertisedV4
.ip
.v4
.b
[0], m
->AdvertisedV4
.ip
.v4
.b
[1], m
->AdvertisedV4
.ip
.v4
.b
[2], m
->AdvertisedV4
.ip
.v4
.b
[3]);
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 propArgs
[3].name
= "NewInternalPort";
659 propArgs
[3].type
= "ui2";
660 propArgs
[3].value
= internalPort
;
661 propArgs
[4].name
= "NewInternalClient";
662 propArgs
[4].type
= "string";
663 propArgs
[4].value
= localIPAddrString
;
664 propArgs
[5].name
= "NewEnabled";
665 propArgs
[5].type
= "boolean";
666 propArgs
[5].value
= "1";
667 propArgs
[6].name
= "NewPortMappingDescription";
668 propArgs
[6].type
= "string";
669 propArgs
[6].value
= publicPortString
;
670 propArgs
[7].name
= "NewLeaseDuration";
671 propArgs
[7].type
= "ui4";
672 propArgs
[7].value
= "0";
674 LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n
->IntPort
), ReqPortNum
);
675 return SendSOAPMsgControlAction(m
, &n
->tcpInfo
, "AddPortMapping", 8, propArgs
, LNTPortMapOp
);
678 mDNSexport mStatus
LNT_MapPort(mDNS
*m
, NATTraversalInfo
*const n
)
680 LogInfo("LNT_MapPort");
681 if (n
->tcpInfo
.sock
) return(mStatus_NoError
); // If we already have a connection up don't make another request for the same thing
682 n
->tcpInfo
.parentNATInfo
= n
;
683 n
->tcpInfo
.retries
= 0;
684 return SendPortMapRequest(m
, n
);
687 mDNSexport mStatus
LNT_UnmapPort(mDNS
*m
, NATTraversalInfo
*const n
)
689 char externalPort
[10];
690 Property propArgs
[3];
692 tcpLNTInfo
**infoPtr
= &m
->tcpInfoUnmapList
;
695 // If no NAT gateway to talk to, no need to do all this work for nothing
696 if (mDNSIPPortIsZero(m
->UPnPSOAPPort
) || !m
->UPnPSOAPURL
|| !m
->UPnPSOAPAddressString
) return mStatus_NoError
;
698 mDNS_snprintf(externalPort
, sizeof(externalPort
), "%u", mDNSVal16(mDNSIPPortIsZero(n
->RequestedPort
) ? n
->IntPort
: n
->RequestedPort
));
700 mDNSPlatformMemZero(propArgs
, sizeof(propArgs
));
701 propArgs
[0].name
= "NewRemoteHost";
702 propArgs
[0].type
= "string";
703 propArgs
[0].value
= "";
704 propArgs
[1].name
= "NewExternalPort";
705 propArgs
[1].type
= "ui2";
706 propArgs
[1].value
= externalPort
;
707 propArgs
[2].name
= "NewProtocol";
708 propArgs
[2].type
= "string";
709 propArgs
[2].value
= (n
->Protocol
== NATOp_MapUDP
) ? "UDP" : "TCP";
711 n
->tcpInfo
.parentNATInfo
= n
;
713 // clean up previous port mapping requests and allocations
714 if (n
->tcpInfo
.sock
) LogInfo("LNT_UnmapPort: closing previous open connection");
715 if (n
->tcpInfo
.sock
) { mDNSPlatformTCPCloseConnection(n
->tcpInfo
.sock
); n
->tcpInfo
.sock
= mDNSNULL
; }
716 if (n
->tcpInfo
.Request
) { mDNSPlatformMemFree(n
->tcpInfo
.Request
); n
->tcpInfo
.Request
= mDNSNULL
; }
717 if (n
->tcpInfo
.Reply
) { mDNSPlatformMemFree(n
->tcpInfo
.Reply
); n
->tcpInfo
.Reply
= mDNSNULL
; }
719 // 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)
720 if ((info
= mDNSPlatformMemAllocate(sizeof(tcpLNTInfo
))) == mDNSNULL
)
721 { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr
); }
724 while (*infoPtr
) infoPtr
= &(*infoPtr
)->next
; // find the end of the list
725 *infoPtr
= info
; // append
727 err
= SendSOAPMsgControlAction(m
, info
, "DeletePortMapping", 3, propArgs
, LNTPortMapDeleteOp
);
728 if (err
) DisposeInfoFromUnmapList(m
, info
);
732 mDNSexport mStatus
LNT_GetExternalAddress(mDNS
*m
)
734 return SendSOAPMsgControlAction(m
, &m
->tcpAddrInfo
, "GetExternalIPAddress", 0, mDNSNULL
, LNTExternalAddrOp
);
737 mDNSlocal mStatus
GetDeviceDescription(mDNS
*m
, tcpLNTInfo
*info
)
739 // Device description format -
740 // - device description URL
742 static const char szSSDPMsgDescribeDeviceFMT
[] =
743 "GET %s HTTP/1.1\r\n"
744 "Accept: text/xml, application/xml\r\n"
745 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n"
747 "Connection: close\r\n"
750 if (!mDNSIPPortIsZero(m
->UPnPSOAPPort
)) return mStatus_NoError
; // already have the info we need
752 if (m
->UPnPRouterURL
== mDNSNULL
|| m
->UPnPRouterAddressString
== mDNSNULL
) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid
); }
755 if (info
->Request
!= mDNSNULL
) mDNSPlatformMemZero(info
->Request
, LNT_MAXBUFSIZE
); // reuse previously allocated buffer
756 else if ((info
->Request
= mDNSPlatformMemAllocate(LNT_MAXBUFSIZE
)) == mDNSNULL
) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr
); }
757 info
->requestLen
= mDNS_snprintf((char*)info
->Request
, LNT_MAXBUFSIZE
, szSSDPMsgDescribeDeviceFMT
, m
->UPnPRouterURL
, m
->UPnPRouterAddressString
);
758 LogInfo("Describe Device: [%s]", info
->Request
);
759 return MakeTCPConnection(m
, info
, &m
->Router
, m
->UPnPRouterPort
, LNTDiscoveryOp
);
762 // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response
763 // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and
765 mDNSexport
void LNT_ConfigureRouterInfo(mDNS
*m
, const mDNSInterfaceID InterfaceID
, const mDNSu8
*const data
, const mDNSu16 len
)
767 const mDNSu8
*ptr
= data
;
768 const mDNSu8
*end
= data
+ len
;
769 const mDNSu8
*stop
= ptr
;
771 if (!mDNSIPPortIsZero(m
->UPnPRouterPort
)) return; // already have the info we need
773 // The formatting of the HTTP header is not always the same when it comes to the placement of
774 // the service and location strings, so we just look for each of them from the beginning for every response
776 // figure out if this is a message from a service we care about
777 while (ptr
&& ptr
!= end
)
779 if ((*ptr
& 0xDF) == 'W' && (strncasecmp((char*)ptr
, "WANIPConnection:1", 17) == 0)) break;
785 while (ptr
&& ptr
!= end
)
787 if ((*ptr
& 0xDF) == 'W' && (strncasecmp((char*)ptr
, "WANPPPConnection:1", 18) == 0)) break;
791 if (ptr
== mDNSNULL
|| ptr
== end
) return; // not a message we care about
793 // find "Location:", starting from the beginning
795 while (ptr
&& ptr
!= end
)
797 if ((*ptr
& 0xDF) == 'L' && (strncasecmp((char*)ptr
, "Location:", 9) == 0)) break; // find the first 'L'; is this Location? if not, keep looking
800 if (ptr
== mDNSNULL
|| ptr
== end
)
802 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.ssdp", "failure", "Location", "");
803 return; // not a message we care about
805 ptr
+= 9; //Skip over 'Location:'
806 while (*ptr
== ' ' && ptr
< end
) ptr
++; // skip over spaces
807 if (ptr
>= end
) return;
809 // find the end of the line
810 for (stop
= ptr
; stop
!= end
; stop
++) { if (*stop
== '\r') { end
= stop
; break; } }
812 // fill in default port
813 m
->UPnPRouterPort
= mDNSOpaque16fromIntVal(80);
815 // free string pointers and set to NULL
816 if (m
->UPnPRouterAddressString
!= mDNSNULL
)
818 mDNSPlatformMemFree(m
->UPnPRouterAddressString
);
819 m
->UPnPRouterAddressString
= mDNSNULL
;
821 if (m
->UPnPRouterURL
!= mDNSNULL
)
823 mDNSPlatformMemFree(m
->UPnPRouterURL
);
824 m
->UPnPRouterURL
= mDNSNULL
;
827 // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc"
828 if (ParseHttpUrl(ptr
, end
, &m
->UPnPRouterAddressString
, &m
->UPnPRouterPort
, &m
->UPnPRouterURL
) != mStatus_NoError
)
830 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.ssdp", "failure", "Parse URL", "");
834 m
->UPnPInterfaceID
= InterfaceID
;
836 if (m
->UPnPRouterAddressString
== mDNSNULL
)
838 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.ssdp", "failure", "Router address", "");
839 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL");
841 else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m
->UPnPRouterAddressString
);
843 if (m
->UPnPRouterURL
== mDNSNULL
)
845 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.ssdp", "failure", "Router path", "");
846 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL");
848 else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m
->UPnPRouterURL
);
850 LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m
->UPnPRouterPort
));
851 LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m
->UPnPInterfaceID
);
853 // Don't need the SSDP socket anymore
854 if (m
->SSDPSocket
) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m
->SSDPSocket
); mDNSPlatformUDPClose(m
->SSDPSocket
); m
->SSDPSocket
= mDNSNULL
; }
856 mDNSASLLog((uuid_t
*)&m
->asl_uuid
, "natt.legacy.ssdp", "success", "success", "");
857 // now send message to get the device description
858 GetDeviceDescription(m
, &m
->tcpDeviceInfo
);
861 mDNSexport
void LNT_SendDiscoveryMsg(mDNS
*m
)
863 static const char msg
[] =
864 "M-SEARCH * HTTP/1.1\r\n"
865 "Host:239.255.255.250:1900\r\n"
866 "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n"
867 "Man:\"ssdp:discover\"\r\n"
869 static const mDNSAddr multicastDest
= { mDNSAddrType_IPv4
, { { { 239, 255, 255, 250 } } } };
871 mDNSu8
*buf
= (mDNSu8
*)&m
->omsg
; //m->omsg is 8952 bytes, which is plenty
874 if (!mDNSIPPortIsZero(m
->UPnPRouterPort
))
876 if (m
->SSDPSocket
) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m
->SSDPSocket
); mDNSPlatformUDPClose(m
->SSDPSocket
); m
->SSDPSocket
= mDNSNULL
; }
877 if (mDNSIPPortIsZero(m
->UPnPSOAPPort
) && !m
->tcpDeviceInfo
.sock
) GetDeviceDescription(m
, &m
->tcpDeviceInfo
);
881 // Always query for WANIPConnection in the first SSDP packet
882 if (m
->retryIntervalGetAddr
<= NATMAP_INIT_RETRY
) m
->SSDPWANPPPConnection
= mDNSfalse
;
885 bufLen
= mDNS_snprintf((char*)buf
, sizeof(m
->omsg
), msg
, m
->SSDPWANPPPConnection
? "PPP" : "IP");
887 debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m
->Router
.ip
.v4
, &m
->ExternalAddress
);
889 if (!mDNSIPv4AddressIsZero(m
->Router
.ip
.v4
))
891 if (!m
->SSDPSocket
) { m
->SSDPSocket
= mDNSPlatformUDPSocket(m
, zeroIPPort
); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m
->SSDPSocket
); }
892 mDNSPlatformSendUDP(m
, buf
, buf
+ bufLen
, 0, m
->SSDPSocket
, &m
->Router
, SSDPPort
);
893 mDNSPlatformSendUDP(m
, buf
, buf
+ bufLen
, 0, m
->SSDPSocket
, &multicastDest
, SSDPPort
);
896 m
->SSDPWANPPPConnection
= !m
->SSDPWANPPPConnection
;
899 mDNSexport
void LNT_ClearState(mDNS
*const m
)
901 if (m
->tcpAddrInfo
.sock
) { mDNSPlatformTCPCloseConnection(m
->tcpAddrInfo
.sock
); m
->tcpAddrInfo
.sock
= mDNSNULL
; }
902 if (m
->tcpDeviceInfo
.sock
) { mDNSPlatformTCPCloseConnection(m
->tcpDeviceInfo
.sock
); m
->tcpDeviceInfo
.sock
= mDNSNULL
; }
903 m
->UPnPSOAPPort
= m
->UPnPRouterPort
= zeroIPPort
; // Reset UPnP ports
906 #endif /* _LEGACY_NAT_TRAVERSAL_ */