Imported Upstream version 878.70.2
[platform/upstream/mdnsresponder.git] / mDNSMacOSX / LegacyNATTraversal.c
1 /* -*- Mode: C; tab-width: 4 -*-
2  *
3  * Copyright (c) 2004-2015 Apple Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #ifdef _LEGACY_NAT_TRAVERSAL_
19
20 #include "stdlib.h"         // For strtol()
21 #include "string.h"         // For strlcpy(), For strncpy(), strncasecmp()
22 #include "assert.h"         // For assert()
23
24 #if defined( WIN32 )
25 #   include "CommonServices.h"
26 #   include <winsock2.h>
27 #   include <ws2tcpip.h>
28 #   define strcasecmp   _stricmp
29 #   define strncasecmp  _strnicmp
30
31 static int
32 inet_pton( int family, const char * addr, void * dst )
33 {
34     struct sockaddr_storage ss;
35     int sslen = sizeof( ss );
36
37     ZeroMemory( &ss, sizeof( ss ) );
38     ss.ss_family = (ADDRESS_FAMILY)family;
39
40     if ( WSAStringToAddressA( (LPSTR)addr, family, NULL, ( struct sockaddr* ) &ss, &sslen ) == 0 )
41     {
42         if ( family == AF_INET ) { memcpy( dst, &( ( struct sockaddr_in* ) &ss)->sin_addr, sizeof( IN_ADDR ) ); return 1; }
43         else if ( family == AF_INET6 ) { memcpy( dst, &( ( struct sockaddr_in6* ) &ss)->sin6_addr, sizeof( IN6_ADDR ) ); return 1; }
44         else return 0;
45     }
46     else return 0;
47 }
48 #else
49 #   include <arpa/inet.h>       // For inet_pton()
50 #endif
51
52 #include "mDNSEmbeddedAPI.h"
53 #include "uDNS.h"           // For natTraversalHandleAddressReply() etc.
54
55 // used to format SOAP port mapping arguments
56 typedef struct Property_struct
57 {
58     char *name;
59     char *type;
60     char *value;
61 } Property;
62
63 // All of the text parsing in this file is intentionally transparent so that we know exactly
64 // what's being done to the text, with an eye towards preventing security problems.
65
66 // This is an evolving list of useful acronyms to know. Please add to it at will.
67 // ST      Service Type
68 // NT      Notification Type
69 // USN     Unique Service Name
70 // UDN     Unique Device Name
71 // UUID    Universally Unique Identifier
72 // URN/urn Universal Resource Name
73
74 // Forward declaration because of circular reference:
75 // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse
76 // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again
77 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n);
78
79 #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (mDNSu16)(n)->tcpInfo.retries)
80
81 // Note that this function assumes src is already NULL terminated
82 mDNSlocal void AllocAndCopy(char **const dst, const char *const src)
83 {
84     if (src == mDNSNULL) return;
85     if ((strlen((char*)src)) >= UINT32_MAX || (*dst = mDNSPlatformMemAllocate((mDNSu32)strlen((char*)src) + 1)) == mDNSNULL)
86     {
87         LogMsg("AllocAndCopy: can't allocate string");
88         return;
89     }
90     strcpy((char*)*dst, (char*)src);
91 }
92
93 // This function does a simple parse of an HTTP URL that may include a hostname, port, and path
94 // 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)
95 mDNSlocal mStatus ParseHttpUrl(const mDNSu8 *ptr, const mDNSu8 *const end, char **const addressAndPort, mDNSIPPort *const port, char **const path)
96 {
97     // if the data begins with "http://", we assume there is a hostname and possibly a port number
98     if (end - ptr >= 7 && strncasecmp((char*)ptr, "http://", 7) == 0)
99     {
100         int i;
101         const mDNSu8 *stop = end;
102         const mDNSu8 *addrPtr = mDNSNULL;
103
104         ptr += 7; //skip over "http://"
105         if (ptr >= end) { LogInfo("ParseHttpUrl: past end of buffer parsing host:port"); return mStatus_BadParamErr; }
106
107         // find the end of the host:port
108         addrPtr = ptr;
109         for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break;
110
111         // allocate the buffer (len i+1 so we have space to terminate the string)
112         if ((*addressAndPort = mDNSPlatformMemAllocate(i+1)) == mDNSNULL)
113         { LogMsg("ParseHttpUrl: can't allocate address string"); return mStatus_NoMemoryErr; }
114         strncpy((char*)*addressAndPort, (char*)ptr, i);
115         (*addressAndPort)[i] = '\0';
116
117         // find the port number in the string, by looking backwards for the ':'
118         stop = ptr;    // can't go back farther than the original start
119         ptr = addrPtr; // move ptr to the path part
120
121         for (addrPtr--; addrPtr>stop; addrPtr--)
122         {
123             if (*addrPtr == ':')
124             {
125                 addrPtr++; // skip over ':'
126                 *port = mDNSOpaque16fromIntVal((mDNSu16)strtol((char*)addrPtr, mDNSNULL, 10)); // store it properly converted
127                 break;
128             }
129         }
130     }
131
132     // ptr should now point to the first character we haven't yet processed
133     // everything that remains is the path
134     if (path && ptr < end)
135     {
136         if ((*path = mDNSPlatformMemAllocate((mDNSu32)(end - ptr) + 1)) == mDNSNULL)
137         { LogMsg("ParseHttpUrl: can't mDNSPlatformMemAllocate path"); return mStatus_NoMemoryErr; }
138         strncpy((char*)*path, (char*)ptr, end - ptr);
139         (*path)[end - ptr] = '\0';
140     }
141
142     return mStatus_NoError;
143 }
144
145 enum
146 {
147     HTTPCode_NeedMoreData = -1, // No code found in stream
148     HTTPCode_Other        = -2, // Valid code other than those below found in stream
149     HTTPCode_Bad          = -3,
150     HTTPCode_200          = 200,
151     HTTPCode_404          = 404,
152     HTTPCode_500          = 500,
153 };
154
155 mDNSlocal mDNSs16 ParseHTTPResponseCode(const mDNSu8 **const data, const mDNSu8 *const end)
156 {
157     const mDNSu8 *ptr = *data;
158     const mDNSu8 *code;
159
160     if (end - ptr < 5) return HTTPCode_NeedMoreData;
161     if (strncasecmp((char*)ptr, "HTTP/", 5) != 0) return HTTPCode_Bad;
162     ptr += 5;
163     // should we care about the HTTP protocol version?
164
165     // look for first space, which must come before first LF
166     while (ptr && ptr != end)
167     {
168         if (*ptr == '\n') return HTTPCode_Bad;
169         if (*ptr == ' ') break;
170         ptr++;
171     }
172     if (ptr == end) return HTTPCode_NeedMoreData;
173     ptr++;
174
175     if (end - ptr < 3) return HTTPCode_NeedMoreData;
176
177     code = ptr;
178     ptr += 3;
179     while (ptr && ptr != end)
180     {
181         if (*ptr == '\n') break;
182         ptr++;
183     }
184     if (ptr == end) return HTTPCode_NeedMoreData;
185     *data = ++ptr;
186
187     if (memcmp((char*)code, "200", 3) == 0) return HTTPCode_200;
188     if (memcmp((char*)code, "404", 3) == 0) return HTTPCode_404;
189     if (memcmp((char*)code, "500", 3) == 0) return HTTPCode_500;
190
191     LogInfo("ParseHTTPResponseCode found unexpected result code: %c%c%c", code[0], code[1], code[2]);
192     return HTTPCode_Other;
193 }
194
195 // This function parses the xml body of the device description response from the router. Basically, we look to
196 // make sure this is a response referencing a service we care about (WANIPConnection or WANPPPConnection),
197 // look for the "controlURL" header immediately following, and copy the addressing and URL info we need
198 mDNSlocal void handleLNTDeviceDescriptionResponse(tcpLNTInfo *tcpInfo)
199 {
200     mDNS    *m    = tcpInfo->m;
201     const mDNSu8 *ptr  = tcpInfo->Reply;
202     const mDNSu8 *end  = tcpInfo->Reply + tcpInfo->nread;
203     const mDNSu8 *stop;
204     mDNSs16 http_result;
205
206     if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return; // already have the info we need
207
208     http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
209     if (http_result == HTTPCode_404) LNT_ClearState(m);
210     if (http_result != HTTPCode_200)
211     {
212         LogInfo("handleLNTDeviceDescriptionResponse: HTTP Result code: %d", http_result);
213         return;
214     }
215
216     // Always reset our flag to use WANIPConnection.  We'll use WANPPPConnection if we find it and don't find WANIPConnection.
217     m->UPnPWANPPPConnection = mDNSfalse;
218
219     // find either service we care about
220     while (ptr && ptr < end)
221     {
222         if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANIPConnection:1", 17) == 0)) break;
223         ptr++;
224     }
225     if (ptr == end)
226     {
227         ptr = tcpInfo->Reply;
228         while (ptr && ptr < end)
229         {
230             if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANPPPConnection:1", 18) == 0))
231             {
232                 m->UPnPWANPPPConnection = mDNStrue;
233                 break;
234             }
235             ptr++;
236         }
237     }
238     if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; }
239
240     // find "controlURL", starting from where we left off
241     while (ptr && ptr < end)
242     {
243         if ((*ptr & 0xDF) == 'C' && (strncasecmp((char*)ptr, "controlURL", 10) == 0)) break;            // find the first 'c'; is this controlURL? if not, keep looking
244         ptr++;
245     }
246     if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; }
247     ptr += 11;                          // skip over "controlURL>"
248     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
249
250     // find the end of the controlURL element
251     for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } }
252
253     // fill in default port
254     m->UPnPSOAPPort = m->UPnPRouterPort;
255
256     // free string pointers and set to NULL
257     if (m->UPnPSOAPAddressString != mDNSNULL)
258     {
259         mDNSPlatformMemFree(m->UPnPSOAPAddressString);
260         m->UPnPSOAPAddressString = mDNSNULL;
261     }
262     if (m->UPnPSOAPURL != mDNSNULL)
263     {
264         mDNSPlatformMemFree(m->UPnPSOAPURL);
265         m->UPnPSOAPURL = mDNSNULL;
266     }
267
268     if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, &m->UPnPSOAPURL) != mStatus_NoError) return;
269     // the SOAPURL should look something like "/uuid:0013-108c-4b3f0000f3dc"
270
271     if (m->UPnPSOAPAddressString == mDNSNULL)
272     {
273         ptr = tcpInfo->Reply;
274         while (ptr && ptr < end)
275         {
276             if ((*ptr & 0xDF) == 'U' && (strncasecmp((char*)ptr, "URLBase", 7) == 0)) break;
277             ptr++;
278         }
279
280         if (ptr < end)      // found URLBase
281         {
282             LogInfo("handleLNTDeviceDescriptionResponse: found URLBase");
283             ptr += 8; // skip over "URLBase>"
284             // find the end of the URLBase element
285             for (stop = ptr; stop < end; stop++) { if (stop && *stop == '<') { end = stop; break; } }
286             if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, mDNSNULL) != mStatus_NoError)
287             {
288                 LogInfo("handleLNTDeviceDescriptionResponse: failed to parse URLBase");
289             }
290         }
291
292         // if all else fails, use the router address string
293         if (m->UPnPSOAPAddressString == mDNSNULL) AllocAndCopy(&m->UPnPSOAPAddressString, m->UPnPRouterAddressString);
294     }
295     if (m->UPnPSOAPAddressString == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPAddressString is NULL");
296     else LogInfo("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m->UPnPSOAPAddressString);
297
298     if (m->UPnPSOAPURL == mDNSNULL) AllocAndCopy(&m->UPnPSOAPURL, m->UPnPRouterURL);
299     if (m->UPnPSOAPURL == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPURL is NULL");
300     else LogInfo("handleLNTDeviceDescriptionResponse: SOAP URL [%s]", m->UPnPSOAPURL);
301 }
302
303 mDNSlocal void handleLNTGetExternalAddressResponse(tcpLNTInfo *tcpInfo)
304 {
305     mDNS       *m = tcpInfo->m;
306     mDNSu16 err = NATErr_None;
307     mDNSv4Addr ExtAddr;
308     const mDNSu8 *ptr = tcpInfo->Reply;
309     const mDNSu8 *end = tcpInfo->Reply + tcpInfo->nread;
310     mDNSu8       *addrend;
311     static char tagname[20] = { 'N','e','w','E','x','t','e','r','n','a','l','I','P','A','d','d','r','e','s','s' };
312     // Array NOT including a terminating nul
313
314 //      LogInfo("handleLNTGetExternalAddressResponse: %s", ptr);
315
316     mDNSs16 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
317     if (http_result == HTTPCode_404) LNT_ClearState(m);
318     if (http_result != HTTPCode_200)
319     {
320         LogInfo("handleLNTGetExternalAddressResponse: HTTP Result code: %d", http_result);
321         return;
322     }
323
324     while (ptr < end && strncasecmp((char*)ptr, tagname, sizeof(tagname))) ptr++;
325     ptr += sizeof(tagname);                     // Skip over "NewExternalIPAddress"
326     while (ptr < end && *ptr != '>') ptr++;
327     ptr += 1;                                   // Skip over ">"
328
329     // Find the end of the address and terminate the string so inet_pton() can convert it
330     // (Might be better to copy this to a local string here -- this is overwriting tcpInfo->Reply in-place
331     addrend = (mDNSu8*)ptr;
332     while (addrend < end && (mDNSIsDigit(*addrend) || *addrend == '.')) addrend++;
333     if (addrend >= end) return;
334     *addrend = 0;
335
336     if (inet_pton(AF_INET, (char*)ptr, &ExtAddr) <= 0)
337     {
338         LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr);
339         err = NATErr_NetFail;
340         ExtAddr = zerov4Addr;
341     }
342     if (!err) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr);
343
344     natTraversalHandleAddressReply(m, err, ExtAddr);
345 }
346
347 mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo)
348 {
349     mDNS             *m         = tcpInfo->m;
350     mDNSIPPort extport   = zeroIPPort;
351     const mDNSu8     *ptr       = tcpInfo->Reply;
352     const mDNSu8     *const end = tcpInfo->Reply + tcpInfo->nread;
353     NATTraversalInfo *natInfo;
354     mDNSs16 http_result;
355
356     for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break;}
357
358     if (!natInfo) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; }
359
360     http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
361     if (http_result == HTTPCode_200)
362     {
363         LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)",
364                 mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries);
365
366         // Make sure to compute extport *before* we zero tcpInfo->retries
367         extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo));
368         tcpInfo->retries = 0;
369         natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE, NATTProtocolUPNPIGD);
370     }
371     else if (http_result == HTTPCode_500)
372     {
373         while (ptr && ptr != end)
374         {
375             if (((*ptr & 0xDF) == 'C' && end - ptr >= 8 && strncasecmp((char*)ptr, "Conflict", 8) == 0) ||
376                 (*ptr == '>' && end - ptr >= 15 && strncasecmp((char*)ptr, ">718</errorCode", 15) == 0))
377             {
378                 if (tcpInfo->retries < 100)
379                 {
380                     tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo);
381                     LogInfo("handleLNTPortMappingResponse: Conflict retry %d", tcpInfo->retries);
382                 }
383                 else
384                 {
385                     LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort));
386                     natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0, NATTProtocolUPNPIGD);
387                 }
388                 return;
389             }
390             ptr++;
391         }
392     }
393     else if (http_result == HTTPCode_Bad) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response");
394     else if (http_result == HTTPCode_Other) LogMsg("handleLNTPortMappingResponse got unexpected response code");
395     else if (http_result == HTTPCode_404) LNT_ClearState(m);
396     if (http_result != HTTPCode_200 && http_result != HTTPCode_500)
397         LogInfo("handleLNTPortMappingResponse: HTTP Result code: %d", http_result);
398 }
399
400 mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo)
401 {
402     tcpLNTInfo **ptr = &m->tcpInfoUnmapList;
403     while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next;
404     if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); }    // If we found it, cut it from our list and free the memory
405 }
406
407 mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err)
408 {
409     mStatus status  = mStatus_NoError;
410     tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context;
411     mDNSBool closed  = mDNSfalse;
412     long n       = 0;
413     long nsent   = 0;
414     static int LNTERRORcount = 0;
415
416     if (tcpInfo->sock != sock)
417     {
418         LogMsg("tcpConnectionCallback: WARNING- tcpInfo->sock(%p) != sock(%p) !!! Printing tcpInfo struct", tcpInfo->sock, sock);
419         LogMsg("tcpConnectionCallback: tcpInfo->Address:Port [%#a:%d] tcpInfo->op[%d] tcpInfo->retries[%d] tcpInfo->Request[%s] tcpInfo->Reply[%s]", 
420                 &tcpInfo->Address, mDNSVal16(tcpInfo->Port), tcpInfo->op, tcpInfo->retries, tcpInfo->Request, tcpInfo->Reply);  
421     }
422         
423     // The handlers below expect to be called with the lock held
424     mDNS_Lock(tcpInfo->m);
425
426     if (err) { LogInfo("tcpConnectionCallback: received error"); goto exit; }
427
428     if (ConnectionEstablished)      // connection is established - send the message
429     {
430         LogInfo("tcpConnectionCallback: connection established, sending message");
431         nsent = mDNSPlatformWriteTCP(sock, (char*)tcpInfo->Request, tcpInfo->requestLen);
432         if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; }
433     }
434     else
435     {
436         n = mDNSPlatformReadTCP(sock, (char*)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed);
437         LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n);
438
439         if      (n < 0)  { LogInfo("tcpConnectionCallback - read returned %d", n);                           status = mStatus_ConnFailed; goto exit; }
440         else if (closed) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; }
441
442         tcpInfo->nread += n;
443         LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread);
444         if (tcpInfo->nread > LNT_MAXBUFSIZE)
445         {
446             LogInfo("result truncated...");
447             tcpInfo->nread = LNT_MAXBUFSIZE;
448         }
449
450         switch (tcpInfo->op)
451         {
452         case LNTDiscoveryOp:     handleLNTDeviceDescriptionResponse (tcpInfo); break;
453         case LNTExternalAddrOp:  handleLNTGetExternalAddressResponse(tcpInfo); break;
454         case LNTPortMapOp:       handleLNTPortMappingResponse       (tcpInfo); break;
455         case LNTPortMapDeleteOp: status = mStatus_ConfigChanged;               break;
456         default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break;
457         }
458     }
459 exit:
460     if (err || status)
461     {
462         mDNS   *m = tcpInfo->m;
463         if ((++LNTERRORcount % 1000) == 0)
464         {   
465             LogMsg("ERROR: tcpconnectioncallback -> got error status %d times", LNTERRORcount);
466             assert(LNTERRORcount < 1000);
467             // Recovery Mechanism to bail mDNSResponder out of trouble: It has been seen that we can get into 
468             // this loop: [tcpKQSocketCallback()--> doTcpSocketCallback()-->tcpconnectionCallback()-->mDNSASLLog()],
469             // if mDNSPlatformTCPCloseConnection() does not close the TCPSocket. Instead of calling mDNSASLLog()
470             // repeatedly and logging the same error msg causing 100% CPU usage, we 
471             // crash mDNSResponder using assert() and restart fresh. See advantages below:
472             // 1.Better User Experience 
473             // 2.CrashLogs frequency can be monitored 
474             // 3.StackTrace can be used for more info 
475         }   
476
477         switch (tcpInfo->op)
478         {
479         case LNTDiscoveryOp:
480             LogInfo("tcpConnectionCallback: DeviceDescription SOAP address %s SOAP path %s",
481                 m->UPnPSOAPAddressString ? m->UPnPSOAPAddressString : "NULL", m->UPnPSOAPURL ? m->UPnPSOAPURL : "NULL");
482             break;
483         case LNTExternalAddrOp:
484             LogInfo("tcpConnectionCallback: AddressRequest %s", mDNSIPv4AddressIsZero(m->ExtAddress) ? "failure" : "success");
485             break;
486         case LNTPortMapOp:
487             if (tcpInfo->parentNATInfo)
488                 LogInfo("tcpConnectionCallback: PortMapRequest %s result %d",
489                     (tcpInfo->parentNATInfo->Result) ? "failure" : "success", tcpInfo->parentNATInfo->Result);
490             break;
491         case LNTPortMapDeleteOp: break;
492         default:                 break;
493         }
494
495         mDNSPlatformTCPCloseConnection(sock);
496         tcpInfo->sock = mDNSNULL;
497         if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; }
498         if (tcpInfo->Reply  ) { mDNSPlatformMemFree(tcpInfo->Reply);   tcpInfo->Reply   = mDNSNULL; }
499     }
500     else
501     {
502         LNTERRORcount = 0;  // clear LNTERRORcount
503     }
504
505     if (tcpInfo) mDNS_Unlock(tcpInfo->m);
506
507     if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo);
508 }
509
510 mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op)
511 {
512     mStatus err = mStatus_NoError;
513     mDNSIPPort srcport = zeroIPPort;
514
515     if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port))
516     { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); }
517     info->m         = m;
518     info->Address   = *Addr;
519     info->Port      = Port;
520     info->op        = op;
521     info->nread     = 0;
522     info->replyLen  = LNT_MAXBUFSIZE;
523     if      (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE);   // reuse previously allocated buffer
524     else if ((info->Reply = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr); }
525
526     if (info->sock) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; }
527     info->sock = mDNSPlatformTCPSocket(kTCPSocketFlags_Zero, &srcport, mDNSfalse);
528     if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); }
529     LogInfo("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port));
530     err = mDNSPlatformTCPConnect(info->sock, Addr, Port, mDNSNULL, 0, tcpConnectionCallback, info);
531
532     if      (err == mStatus_ConnPending) err = mStatus_NoError;
533     else if (err == mStatus_ConnEstablished)
534     {
535         mDNS_DropLockBeforeCallback();
536         tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError);
537         mDNS_ReclaimLockAfterCallback();
538         err = mStatus_NoError;
539     }
540     else
541     {
542         // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc.
543         LogInfo("LNT MakeTCPConnection: connection failed");
544         mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above
545         info->sock = mDNSNULL;
546         mDNSPlatformMemFree(info->Reply);
547         info->Reply = mDNSNULL;
548     }
549     return(err);
550 }
551
552 mDNSlocal unsigned int AddSOAPArguments(char *const buf, const unsigned int maxlen, const int numArgs, const Property *const a)
553 {
554     static const char f1[] = "<%s>%s</%s>";
555     static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>";
556     int i, len = 0;
557     *buf = 0;
558     for (i = 0; i < numArgs; i++)
559     {
560         if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name);
561         else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name,            a[i].value, a[i].name);
562     }
563     return(len);
564 }
565
566 mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, const char *const Action, const int numArgs, const Property *const Arguments, const LNTOp_t op)
567 {
568     // SOAP message header format -
569     //  - control URL
570     //  - action (string)
571     //  - router's host/port ("host:port")
572     //  - content-length
573     static const char header[] =
574         "POST %s HTTP/1.1\r\n"
575         "Content-Type: text/xml; charset=\"utf-8\"\r\n"
576         "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n"
577         "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n"
578         "Host: %s\r\n"
579         "Content-Length: %d\r\n"
580         "Connection: close\r\n"
581         "Pragma: no-cache\r\n"
582         "\r\n"
583         "%s\r\n";
584
585     static const char body1[] =
586         "<?xml version=\"1.0\"?>\r\n"
587         "<SOAP-ENV:Envelope"
588         " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
589         " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
590         "<SOAP-ENV:Body>"
591         "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">";
592
593     static const char body2[] =
594         "</m:%s>"
595         "</SOAP-ENV:Body>"
596         "</SOAP-ENV:Envelope>\r\n";
597
598     mStatus err;
599     char   *body = (char*)&m->omsg;         // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty
600     int bodyLen;
601
602     if (mDNSIPPortIsZero(m->UPnPSOAPPort) || m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL)    // if no SOAP URL or address exists get out here
603     { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid; }
604
605     // Create body
606     bodyLen  = mDNS_snprintf   (body,           sizeof(m->omsg),           body1,   Action,   m->UPnPWANPPPConnection ? "PPP" : "IP");
607     bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments);
608     bodyLen += mDNS_snprintf   (body + bodyLen, sizeof(m->omsg) - bodyLen, body2,   Action);
609
610     // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field
611     if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE);
612     if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; }
613     info->requestLen = mDNS_snprintf((char*)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body);
614
615     err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op);
616     if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; }
617     return err;
618 }
619
620 // Build port mapping request with new port (up to max) and send it
621 mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n)
622 {
623     char externalPort[6];
624     char internalPort[6];
625     char localIPAddrString[30];
626     char publicPortString[40];
627     Property propArgs[8];
628     mDNSu16 ReqPortNum = RequestedPortNum(n);
629     NATTraversalInfo *n2 = m->NATTraversals;
630
631     // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique.
632     // UPnP gateways will report conflicts if different devices request the same external port, but if two
633     // clients on the same device request the same external port the second one just stomps over the first.
634     // One way this can happen is like this:
635     // 1. Client A binds local port 80
636     // 2. Client A requests external port 80 -> internal port 80
637     // 3. UPnP NAT gateway refuses external port 80 (some other client already has it)
638     // 4. Client A tries again, and successfully gets external port 80 -> internal port 81
639     // 5. Client B on same machine tries to bind local port 80, and fails
640     // 6. Client B tries again, and successfully binds local port 81
641     // 7. Client B now requests external port 81 -> internal port 81
642     // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping
643
644     while (n2)
645     {
646         if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next;
647         else
648         {
649             if (n->tcpInfo.retries < 100)
650             {
651                 n->tcpInfo.retries++;
652                 ReqPortNum = RequestedPortNum(n);   // Pick a new port number
653                 n2 = m->NATTraversals;              // And re-scan the list looking for conflicts
654             }
655             else
656             {
657                 natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0, NATTProtocolUPNPIGD);
658                 return mStatus_NoError;
659             }
660         }
661     }
662
663     // create strings to use in the message
664     mDNS_snprintf(externalPort,      sizeof(externalPort),      "%u",   ReqPortNum);
665     mDNS_snprintf(internalPort,      sizeof(internalPort),      "%u",   mDNSVal16(n->IntPort));
666     mDNS_snprintf(publicPortString,  sizeof(publicPortString),  "iC%u", ReqPortNum);
667     mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u",
668                   m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]);
669
670     // build the message
671     mDNSPlatformMemZero(propArgs, sizeof(propArgs));
672     propArgs[0].name  = "NewRemoteHost";
673     propArgs[0].type  = "string";
674     propArgs[0].value = "";
675     propArgs[1].name  = "NewExternalPort";
676     propArgs[1].type  = "ui2";
677     propArgs[1].value = externalPort;
678     propArgs[2].name  = "NewProtocol";
679     propArgs[2].type  = "string";
680     propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
681     propArgs[3].name  = "NewInternalPort";
682     propArgs[3].type  = "ui2";
683     propArgs[3].value = internalPort;
684     propArgs[4].name  = "NewInternalClient";
685     propArgs[4].type  = "string";
686     propArgs[4].value = localIPAddrString;
687     propArgs[5].name  = "NewEnabled";
688     propArgs[5].type  = "boolean";
689     propArgs[5].value = "1";
690     propArgs[6].name  = "NewPortMappingDescription";
691     propArgs[6].type  = "string";
692     propArgs[6].value = publicPortString;
693     propArgs[7].name  = "NewLeaseDuration";
694     propArgs[7].type  = "ui4";
695     propArgs[7].value = "0";
696
697     LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum);
698     return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp);
699 }
700
701 mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *const n)
702 {
703     LogInfo("LNT_MapPort");
704     if (n->tcpInfo.sock) return(mStatus_NoError);   // If we already have a connection up don't make another request for the same thing
705     n->tcpInfo.parentNATInfo = n;
706     n->tcpInfo.retries       = 0;
707     return SendPortMapRequest(m, n);
708 }
709
710 mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *const n)
711 {
712     char externalPort[10];
713     Property propArgs[3];
714     tcpLNTInfo  *info;
715     tcpLNTInfo  **infoPtr = &m->tcpInfoUnmapList;
716     mStatus err;
717
718     // If no NAT gateway to talk to, no need to do all this work for nothing
719     if (mDNSIPPortIsZero(m->UPnPSOAPPort) || !m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError;
720
721     mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort));
722
723     mDNSPlatformMemZero(propArgs, sizeof(propArgs));
724     propArgs[0].name  = "NewRemoteHost";
725     propArgs[0].type  = "string";
726     propArgs[0].value = "";
727     propArgs[1].name  = "NewExternalPort";
728     propArgs[1].type  = "ui2";
729     propArgs[1].value = externalPort;
730     propArgs[2].name  = "NewProtocol";
731     propArgs[2].type  = "string";
732     propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
733
734     n->tcpInfo.parentNATInfo = n;
735
736     // clean up previous port mapping requests and allocations
737     if (n->tcpInfo.sock) LogInfo("LNT_UnmapPort: closing previous open connection");
738     if (n->tcpInfo.sock   ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock    = mDNSNULL; }
739     if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request);         n->tcpInfo.Request = mDNSNULL; }
740     if (n->tcpInfo.Reply  ) { mDNSPlatformMemFree(n->tcpInfo.Reply);           n->tcpInfo.Reply   = mDNSNULL; }
741
742     // 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)
743     if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL)
744     { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); }
745     *info = n->tcpInfo;
746
747     while (*infoPtr) infoPtr = &(*infoPtr)->next;   // find the end of the list
748     *infoPtr = info;    // append
749
750     err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp);
751     if (err) DisposeInfoFromUnmapList(m, info);
752     return err;
753 }
754
755 mDNSexport mStatus LNT_GetExternalAddress(mDNS *m)
756 {
757     return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp);
758 }
759
760 mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info)
761 {
762     // Device description format -
763     //  - device description URL
764     //  - host/port
765     static const char szSSDPMsgDescribeDeviceFMT[] =
766         "GET %s HTTP/1.1\r\n"
767         "Accept: text/xml, application/xml\r\n"
768         "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n"
769         "Host: %s\r\n"
770         "Connection: close\r\n"
771         "\r\n";
772
773     if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return mStatus_NoError; // already have the info we need
774
775     if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); }
776
777     // build message
778     if      (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer
779     else if ((info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); }
780     info->requestLen = mDNS_snprintf((char*)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString);
781     LogInfo("Describe Device: [%s]", info->Request);
782     return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp);
783 }
784
785 // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response
786 // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and
787 // URL info we need.
788 mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, const mDNSu8 *const data, const mDNSu16 len)
789 {
790     const mDNSu8 *ptr = data;
791     const mDNSu8 *end = data + len;
792     const mDNSu8 *stop;
793
794     if (!mDNSIPPortIsZero(m->UPnPRouterPort)) return; // already have the info we need
795
796     // The formatting of the HTTP header is not always the same when it comes to the placement of
797     // the service and location strings, so we just look for each of them from the beginning for every response
798
799     // figure out if this is a message from a service we care about
800     while (ptr && ptr != end)
801     {
802         if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANIPConnection:1", 17) == 0)) break;
803         ptr++;
804     }
805     if (ptr == end)
806     {
807         ptr = data;
808         while (ptr && ptr != end)
809         {
810             if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANPPPConnection:1", 18) == 0)) break;
811             ptr++;
812         }
813     }
814     if (ptr == mDNSNULL || ptr == end) return;  // not a message we care about
815
816     // find "Location:", starting from the beginning
817     ptr = data;
818     while (ptr && ptr != end)
819     {
820         if ((*ptr & 0xDF) == 'L' && (strncasecmp((char*)ptr, "Location:", 9) == 0)) break;          // find the first 'L'; is this Location? if not, keep looking
821         ptr++;
822     }
823     if (ptr == mDNSNULL || ptr == end)
824     {
825         LogInfo("LNT_ConfigureRouterInfo: Location field not found");
826         return; // not a message we care about
827     }
828     ptr += 9; //Skip over 'Location:'
829     while (*ptr == ' ' && ptr < end) ptr++; // skip over spaces
830     if (ptr >= end) return;
831
832     // find the end of the line
833     for (stop = ptr; stop != end; stop++) { if (*stop == '\r') { end = stop; break; } }
834
835     // fill in default port
836     m->UPnPRouterPort = mDNSOpaque16fromIntVal(80);
837
838     // free string pointers and set to NULL
839     if (m->UPnPRouterAddressString != mDNSNULL)
840     {
841         mDNSPlatformMemFree(m->UPnPRouterAddressString);
842         m->UPnPRouterAddressString = mDNSNULL;
843     }
844     if (m->UPnPRouterURL != mDNSNULL)
845     {
846         mDNSPlatformMemFree(m->UPnPRouterURL);
847         m->UPnPRouterURL = mDNSNULL;
848     }
849
850     // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc"
851     if (ParseHttpUrl(ptr, end, &m->UPnPRouterAddressString, &m->UPnPRouterPort, &m->UPnPRouterURL) != mStatus_NoError)
852     {
853         LogInfo("LNT_ConfigureRouterInfo: Failed to parse URL");
854         return;
855     }
856
857     m->UPnPInterfaceID = InterfaceID;
858
859     if (m->UPnPRouterAddressString == mDNSNULL)
860     {
861         LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL");
862     }
863     else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m->UPnPRouterAddressString);
864
865     if (m->UPnPRouterURL == mDNSNULL)
866     {
867         LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL");
868     }
869     else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m->UPnPRouterURL);
870
871     LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m->UPnPRouterPort));
872     LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m->UPnPInterfaceID);
873
874     // Don't need the SSDP socket anymore
875     if (m->SSDPSocket) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
876
877     // now send message to get the device description
878     GetDeviceDescription(m, &m->tcpDeviceInfo);
879 }
880
881 mDNSexport void LNT_SendDiscoveryMsg(mDNS *m)
882 {
883     static const char msg[] =
884         "M-SEARCH * HTTP/1.1\r\n"
885         "Host:239.255.255.250:1900\r\n"
886         "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n"
887         "Man:\"ssdp:discover\"\r\n"
888         "MX:3\r\n\r\n";
889     static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } };
890
891     mDNSu8 *const buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
892     unsigned int bufLen;
893
894     if (m->SleepState != SleepState_Awake) return;
895     if (!mDNSIPPortIsZero(m->UPnPRouterPort))
896     {
897         if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
898         if (mDNSIPPortIsZero(m->UPnPSOAPPort) && !m->tcpDeviceInfo.sock) GetDeviceDescription(m, &m->tcpDeviceInfo);
899         return;
900     }
901
902     // Always query for WANIPConnection in the first SSDP packet
903     if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse;
904
905     // Create message
906     bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP");
907
908     debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExtAddress);
909
910     if (!mDNSIPv4AddressIsZero(m->Router.ip.v4))
911     {
912         if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(zeroIPPort); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); }
913         mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router,     SSDPPort, mDNSfalse);
914         mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort, mDNSfalse);
915     }
916
917     m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection;
918 }
919
920 mDNSexport void LNT_ClearState(mDNS *const m)
921 {
922     if (m->tcpAddrInfo.sock)   { mDNSPlatformTCPCloseConnection(m->tcpAddrInfo.sock);   m->tcpAddrInfo.sock   = mDNSNULL; }
923     if (m->tcpDeviceInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpDeviceInfo.sock); m->tcpDeviceInfo.sock = mDNSNULL; }
924     m->UPnPSOAPPort = m->UPnPRouterPort = zeroIPPort;   // Reset UPnP ports
925 }
926
927 #endif /* _LEGACY_NAT_TRAVERSAL_ */