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