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 | # define mDNSASLLog( UUID, SUBDOMAIN, RESULT, SIGNATURE, FORMAT, ... ) ; |
---|
31 | |
---|
32 | static int |
---|
33 | inet_pton( int family, const char * addr, void * dst ) |
---|
34 | { |
---|
35 | struct sockaddr_storage ss; |
---|
36 | int sslen = sizeof( ss ); |
---|
37 | |
---|
38 | ZeroMemory( &ss, sizeof( ss ) ); |
---|
39 | ss.ss_family = (ADDRESS_FAMILY)family; |
---|
40 | |
---|
41 | if ( WSAStringToAddressA( (LPSTR)addr, family, NULL, ( struct sockaddr* ) &ss, &sslen ) == 0 ) |
---|
42 | { |
---|
43 | if ( family == AF_INET ) { memcpy( dst, &( ( struct sockaddr_in* ) &ss)->sin_addr, sizeof( IN_ADDR ) ); return 1; } |
---|
44 | else if ( family == AF_INET6 ) { memcpy( dst, &( ( struct sockaddr_in6* ) &ss)->sin6_addr, sizeof( IN6_ADDR ) ); return 1; } |
---|
45 | else return 0; |
---|
46 | } |
---|
47 | else return 0; |
---|
48 | } |
---|
49 | #else |
---|
50 | # include <arpa/inet.h> // For inet_pton() |
---|
51 | #endif |
---|
52 | |
---|
53 | #include "mDNSEmbeddedAPI.h" |
---|
54 | #include "uDNS.h" // For natTraversalHandleAddressReply() etc. |
---|
55 | |
---|
56 | // used to format SOAP port mapping arguments |
---|
57 | typedef struct Property_struct |
---|
58 | { |
---|
59 | char *name; |
---|
60 | char *type; |
---|
61 | char *value; |
---|
62 | } Property; |
---|
63 | |
---|
64 | // All of the text parsing in this file is intentionally transparent so that we know exactly |
---|
65 | // what's being done to the text, with an eye towards preventing security problems. |
---|
66 | |
---|
67 | // This is an evolving list of useful acronyms to know. Please add to it at will. |
---|
68 | // ST Service Type |
---|
69 | // NT Notification Type |
---|
70 | // USN Unique Service Name |
---|
71 | // UDN Unique Device Name |
---|
72 | // UUID Universally Unique Identifier |
---|
73 | // URN/urn Universal Resource Name |
---|
74 | |
---|
75 | // Forward declaration because of circular reference: |
---|
76 | // SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse |
---|
77 | // In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again |
---|
78 | mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n); |
---|
79 | |
---|
80 | #define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (mDNSu16)(n)->tcpInfo.retries) |
---|
81 | |
---|
82 | // Note that this function assumes src is already NULL terminated |
---|
83 | mDNSlocal void AllocAndCopy(mDNSu8 **const dst, const mDNSu8 *const src) |
---|
84 | { |
---|
85 | if (src == mDNSNULL) return; |
---|
86 | if ((strlen((char*)src)) >= UINT32_MAX || (*dst = mDNSPlatformMemAllocate((mDNSu32)strlen((char*)src) + 1)) == mDNSNULL) |
---|
87 | { |
---|
88 | LogMsg("AllocAndCopy: can't allocate string"); |
---|
89 | return; |
---|
90 | } |
---|
91 | strcpy((char*)*dst, (char*)src); |
---|
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) |
---|
96 | mDNSlocal mStatus ParseHttpUrl(const mDNSu8 *ptr, const mDNSu8 *const end, mDNSu8 **const addressAndPort, mDNSIPPort *const port, mDNSu8 **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 | |
---|
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 |
---|
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 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "noop", "HTTP Result", "HTTP 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 | |
---|
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 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "HTTP Result", "HTTP 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 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "inet_pton", ""); |
---|
340 | LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr); |
---|
341 | err = NATErr_NetFail; |
---|
342 | ExtAddr = zerov4Addr; |
---|
343 | } |
---|
344 | if (!err) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr); |
---|
345 | |
---|
346 | natTraversalHandleAddressReply(m, err, ExtAddr); |
---|
347 | } |
---|
348 | |
---|
349 | mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo) |
---|
350 | { |
---|
351 | mDNS *m = tcpInfo->m; |
---|
352 | mDNSIPPort extport = zeroIPPort; |
---|
353 | const mDNSu8 *ptr = tcpInfo->Reply; |
---|
354 | const mDNSu8 *const end = tcpInfo->Reply + tcpInfo->nread; |
---|
355 | NATTraversalInfo *natInfo; |
---|
356 | mDNSs16 http_result; |
---|
357 | |
---|
358 | for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break;} |
---|
359 | |
---|
360 | if (!natInfo) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; } |
---|
361 | |
---|
362 | http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr |
---|
363 | if (http_result == HTTPCode_200) |
---|
364 | { |
---|
365 | LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)", |
---|
366 | mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries); |
---|
367 | |
---|
368 | // Make sure to compute extport *before* we zero tcpInfo->retries |
---|
369 | extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo)); |
---|
370 | tcpInfo->retries = 0; |
---|
371 | natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE, NATTProtocolUPNPIGD); |
---|
372 | } |
---|
373 | else if (http_result == HTTPCode_500) |
---|
374 | { |
---|
375 | while (ptr && ptr != end) |
---|
376 | { |
---|
377 | if (((*ptr & 0xDF) == 'C' && end - ptr >= 8 && strncasecmp((char*)ptr, "Conflict", 8) == 0) || |
---|
378 | (*ptr == '>' && end - ptr >= 15 && strncasecmp((char*)ptr, ">718</errorCode", 15) == 0)) |
---|
379 | { |
---|
380 | if (tcpInfo->retries < 100) |
---|
381 | { |
---|
382 | tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo); |
---|
383 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict", "Retry %d", tcpInfo->retries); |
---|
384 | } |
---|
385 | else |
---|
386 | { |
---|
387 | LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort)); |
---|
388 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict - too many retries", "Retries: %d", tcpInfo->retries); |
---|
389 | natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0, NATTProtocolUPNPIGD); |
---|
390 | } |
---|
391 | return; |
---|
392 | } |
---|
393 | ptr++; |
---|
394 | } |
---|
395 | } |
---|
396 | else if (http_result == HTTPCode_Bad) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response"); |
---|
397 | else if (http_result == HTTPCode_Other) LogMsg("handleLNTPortMappingResponse got unexpected response code"); |
---|
398 | else if (http_result == HTTPCode_404) LNT_ClearState(m); |
---|
399 | if (http_result != HTTPCode_200 && http_result != HTTPCode_500) |
---|
400 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "HTTP Result", "HTTP code: %d", http_result); |
---|
401 | } |
---|
402 | |
---|
403 | mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo) |
---|
404 | { |
---|
405 | tcpLNTInfo **ptr = &m->tcpInfoUnmapList; |
---|
406 | while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next; |
---|
407 | if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); } // If we found it, cut it from our list and free the memory |
---|
408 | } |
---|
409 | |
---|
410 | mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err) |
---|
411 | { |
---|
412 | mStatus status = mStatus_NoError; |
---|
413 | tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context; |
---|
414 | mDNSBool closed = mDNSfalse; |
---|
415 | long n = 0; |
---|
416 | long nsent = 0; |
---|
417 | static int LNTERRORcount = 0; |
---|
418 | |
---|
419 | if (tcpInfo->sock != sock) |
---|
420 | { |
---|
421 | LogMsg("tcpConnectionCallback: WARNING- tcpInfo->sock(%p) != sock(%p) !!! Printing tcpInfo struct", tcpInfo->sock, sock); |
---|
422 | LogMsg("tcpConnectionCallback: tcpInfo->Address:Port [%#a:%d] tcpInfo->op[%d] tcpInfo->retries[%d] tcpInfo->Request[%s] tcpInfo->Reply[%s]", |
---|
423 | &tcpInfo->Address, mDNSVal16(tcpInfo->Port), tcpInfo->op, tcpInfo->retries, tcpInfo->Request, tcpInfo->Reply); |
---|
424 | } |
---|
425 | |
---|
426 | // The handlers below expect to be called with the lock held |
---|
427 | mDNS_Lock(tcpInfo->m); |
---|
428 | |
---|
429 | if (err) { LogInfo("tcpConnectionCallback: received error"); goto exit; } |
---|
430 | |
---|
431 | if (ConnectionEstablished) // connection is established - send the message |
---|
432 | { |
---|
433 | LogInfo("tcpConnectionCallback: connection established, sending message"); |
---|
434 | nsent = mDNSPlatformWriteTCP(sock, (char*)tcpInfo->Request, tcpInfo->requestLen); |
---|
435 | if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; } |
---|
436 | } |
---|
437 | else |
---|
438 | { |
---|
439 | n = mDNSPlatformReadTCP(sock, (char*)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed); |
---|
440 | LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n); |
---|
441 | |
---|
442 | if (n < 0) { LogInfo("tcpConnectionCallback - read returned %d", n); status = mStatus_ConnFailed; goto exit; } |
---|
443 | else if (closed) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; } |
---|
444 | |
---|
445 | tcpInfo->nread += n; |
---|
446 | LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread); |
---|
447 | if (tcpInfo->nread > LNT_MAXBUFSIZE) |
---|
448 | { |
---|
449 | LogInfo("result truncated..."); |
---|
450 | tcpInfo->nread = LNT_MAXBUFSIZE; |
---|
451 | } |
---|
452 | |
---|
453 | switch (tcpInfo->op) |
---|
454 | { |
---|
455 | case LNTDiscoveryOp: handleLNTDeviceDescriptionResponse (tcpInfo); break; |
---|
456 | case LNTExternalAddrOp: handleLNTGetExternalAddressResponse(tcpInfo); break; |
---|
457 | case LNTPortMapOp: handleLNTPortMappingResponse (tcpInfo); break; |
---|
458 | case LNTPortMapDeleteOp: status = mStatus_ConfigChanged; break; |
---|
459 | default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break; |
---|
460 | } |
---|
461 | } |
---|
462 | exit: |
---|
463 | if (err || status) |
---|
464 | { |
---|
465 | mDNS *m = tcpInfo->m; |
---|
466 | if ((++LNTERRORcount % 1000) == 0) |
---|
467 | { |
---|
468 | LogMsg("ERROR: tcpconnectioncallback -> got error status %d times", LNTERRORcount); |
---|
469 | assert(LNTERRORcount < 1000); |
---|
470 | // Recovery Mechanism to bail mDNSResponder out of trouble: It has been seen that we can get into |
---|
471 | // this loop: [tcpKQSocketCallback()--> doTcpSocketCallback()-->tcpconnectionCallback()-->mDNSASLLog()], |
---|
472 | // if mDNSPlatformTCPCloseConnection() does not close the TCPSocket. Instead of calling mDNSASLLog() |
---|
473 | // repeatedly and logging the same error msg causing 100% CPU usage, we |
---|
474 | // crash mDNSResponder using assert() and restart fresh. See advantages below: |
---|
475 | // 1.Better User Experience |
---|
476 | // 2.CrashLogs frequency can be monitored |
---|
477 | // 3.StackTrace can be used for more info |
---|
478 | } |
---|
479 | |
---|
480 | switch (tcpInfo->op) |
---|
481 | { |
---|
482 | case LNTDiscoveryOp: if (m->UPnPSOAPAddressString == mDNSNULL) |
---|
483 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP Address", ""); |
---|
484 | if (m->UPnPSOAPURL == mDNSNULL) |
---|
485 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP path", ""); |
---|
486 | if (m->UPnPSOAPAddressString && m->UPnPSOAPURL) |
---|
487 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "success", "success", ""); |
---|
488 | break; |
---|
489 | case LNTExternalAddrOp: mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", |
---|
490 | mDNSIPv4AddressIsZero(m->ExtAddress) ? "failure" : "success", |
---|
491 | mDNSIPv4AddressIsZero(m->ExtAddress) ? "failure" : "success", ""); |
---|
492 | break; |
---|
493 | case LNTPortMapOp: if (tcpInfo->parentNATInfo) |
---|
494 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", (tcpInfo->parentNATInfo->Result) ? "failure" : "success", |
---|
495 | (tcpInfo->parentNATInfo->Result) ? "failure" : "success", "Result: %d", tcpInfo->parentNATInfo->Result); |
---|
496 | break; |
---|
497 | case LNTPortMapDeleteOp: break; |
---|
498 | default: break; |
---|
499 | } |
---|
500 | |
---|
501 | mDNSPlatformTCPCloseConnection(sock); |
---|
502 | tcpInfo->sock = mDNSNULL; |
---|
503 | if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; } |
---|
504 | if (tcpInfo->Reply ) { mDNSPlatformMemFree(tcpInfo->Reply); tcpInfo->Reply = mDNSNULL; } |
---|
505 | } |
---|
506 | else |
---|
507 | { |
---|
508 | LNTERRORcount = 0; // clear LNTERRORcount |
---|
509 | } |
---|
510 | |
---|
511 | if (tcpInfo) mDNS_Unlock(tcpInfo->m); |
---|
512 | |
---|
513 | if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo); |
---|
514 | } |
---|
515 | |
---|
516 | mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op) |
---|
517 | { |
---|
518 | mStatus err = mStatus_NoError; |
---|
519 | mDNSIPPort srcport = zeroIPPort; |
---|
520 | |
---|
521 | if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port)) |
---|
522 | { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); } |
---|
523 | info->m = m; |
---|
524 | info->Address = *Addr; |
---|
525 | info->Port = Port; |
---|
526 | info->op = op; |
---|
527 | info->nread = 0; |
---|
528 | info->replyLen = LNT_MAXBUFSIZE; |
---|
529 | if (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE); // reuse previously allocated buffer |
---|
530 | else if ((info->Reply = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr); } |
---|
531 | |
---|
532 | if (info->sock) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; } |
---|
533 | info->sock = mDNSPlatformTCPSocket(m, kTCPSocketFlags_Zero, &srcport, mDNSfalse); |
---|
534 | if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); } |
---|
535 | LogInfo("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port)); |
---|
536 | err = mDNSPlatformTCPConnect(info->sock, Addr, Port, mDNSNULL, 0, tcpConnectionCallback, info); |
---|
537 | |
---|
538 | if (err == mStatus_ConnPending) err = mStatus_NoError; |
---|
539 | else if (err == mStatus_ConnEstablished) |
---|
540 | { |
---|
541 | mDNS_DropLockBeforeCallback(); |
---|
542 | tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError); |
---|
543 | mDNS_ReclaimLockAfterCallback(); |
---|
544 | err = mStatus_NoError; |
---|
545 | } |
---|
546 | else |
---|
547 | { |
---|
548 | // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc. |
---|
549 | LogInfo("LNT MakeTCPConnection: connection failed"); |
---|
550 | mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above |
---|
551 | info->sock = mDNSNULL; |
---|
552 | mDNSPlatformMemFree(info->Reply); |
---|
553 | info->Reply = mDNSNULL; |
---|
554 | } |
---|
555 | return(err); |
---|
556 | } |
---|
557 | |
---|
558 | mDNSlocal unsigned int AddSOAPArguments(char *const buf, const unsigned int maxlen, const int numArgs, const Property *const a) |
---|
559 | { |
---|
560 | static const char f1[] = "<%s>%s</%s>"; |
---|
561 | static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>"; |
---|
562 | int i, len = 0; |
---|
563 | *buf = 0; |
---|
564 | for (i = 0; i < numArgs; i++) |
---|
565 | { |
---|
566 | if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name); |
---|
567 | else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name, a[i].value, a[i].name); |
---|
568 | } |
---|
569 | return(len); |
---|
570 | } |
---|
571 | |
---|
572 | mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, const char *const Action, const int numArgs, const Property *const Arguments, const LNTOp_t op) |
---|
573 | { |
---|
574 | // SOAP message header format - |
---|
575 | // - control URL |
---|
576 | // - action (string) |
---|
577 | // - router's host/port ("host:port") |
---|
578 | // - content-length |
---|
579 | static const char header[] = |
---|
580 | "POST %s HTTP/1.1\r\n" |
---|
581 | "Content-Type: text/xml; charset=\"utf-8\"\r\n" |
---|
582 | "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n" |
---|
583 | "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n" |
---|
584 | "Host: %s\r\n" |
---|
585 | "Content-Length: %d\r\n" |
---|
586 | "Connection: close\r\n" |
---|
587 | "Pragma: no-cache\r\n" |
---|
588 | "\r\n" |
---|
589 | "%s\r\n"; |
---|
590 | |
---|
591 | static const char body1[] = |
---|
592 | "<?xml version=\"1.0\"?>\r\n" |
---|
593 | "<SOAP-ENV:Envelope" |
---|
594 | " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"" |
---|
595 | " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" |
---|
596 | "<SOAP-ENV:Body>" |
---|
597 | "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">"; |
---|
598 | |
---|
599 | static const char body2[] = |
---|
600 | "</m:%s>" |
---|
601 | "</SOAP-ENV:Body>" |
---|
602 | "</SOAP-ENV:Envelope>\r\n"; |
---|
603 | |
---|
604 | mStatus err; |
---|
605 | char *body = (char*)&m->omsg; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty |
---|
606 | int bodyLen; |
---|
607 | |
---|
608 | if (mDNSIPPortIsZero(m->UPnPSOAPPort) || m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL) // if no SOAP URL or address exists get out here |
---|
609 | { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid; } |
---|
610 | |
---|
611 | // Create body |
---|
612 | bodyLen = mDNS_snprintf (body, sizeof(m->omsg), body1, Action, m->UPnPWANPPPConnection ? "PPP" : "IP"); |
---|
613 | bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments); |
---|
614 | bodyLen += mDNS_snprintf (body + bodyLen, sizeof(m->omsg) - bodyLen, body2, Action); |
---|
615 | |
---|
616 | // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field |
---|
617 | if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE); |
---|
618 | if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; } |
---|
619 | info->requestLen = mDNS_snprintf((char*)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body); |
---|
620 | |
---|
621 | err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op); |
---|
622 | if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; } |
---|
623 | return err; |
---|
624 | } |
---|
625 | |
---|
626 | // Build port mapping request with new port (up to max) and send it |
---|
627 | mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n) |
---|
628 | { |
---|
629 | char externalPort[6]; |
---|
630 | char internalPort[6]; |
---|
631 | char localIPAddrString[30]; |
---|
632 | char publicPortString[40]; |
---|
633 | Property propArgs[8]; |
---|
634 | mDNSu16 ReqPortNum = RequestedPortNum(n); |
---|
635 | NATTraversalInfo *n2 = m->NATTraversals; |
---|
636 | |
---|
637 | // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique. |
---|
638 | // UPnP gateways will report conflicts if different devices request the same external port, but if two |
---|
639 | // clients on the same device request the same external port the second one just stomps over the first. |
---|
640 | // One way this can happen is like this: |
---|
641 | // 1. Client A binds local port 80 |
---|
642 | // 2. Client A requests external port 80 -> internal port 80 |
---|
643 | // 3. UPnP NAT gateway refuses external port 80 (some other client already has it) |
---|
644 | // 4. Client A tries again, and successfully gets external port 80 -> internal port 81 |
---|
645 | // 5. Client B on same machine tries to bind local port 80, and fails |
---|
646 | // 6. Client B tries again, and successfully binds local port 81 |
---|
647 | // 7. Client B now requests external port 81 -> internal port 81 |
---|
648 | // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping |
---|
649 | |
---|
650 | while (n2) |
---|
651 | { |
---|
652 | if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next; |
---|
653 | else |
---|
654 | { |
---|
655 | if (n->tcpInfo.retries < 100) |
---|
656 | { |
---|
657 | n->tcpInfo.retries++; |
---|
658 | ReqPortNum = RequestedPortNum(n); // Pick a new port number |
---|
659 | n2 = m->NATTraversals; // And re-scan the list looking for conflicts |
---|
660 | } |
---|
661 | else |
---|
662 | { |
---|
663 | natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0, NATTProtocolUPNPIGD); |
---|
664 | return mStatus_NoError; |
---|
665 | } |
---|
666 | } |
---|
667 | } |
---|
668 | |
---|
669 | // create strings to use in the message |
---|
670 | mDNS_snprintf(externalPort, sizeof(externalPort), "%u", ReqPortNum); |
---|
671 | mDNS_snprintf(internalPort, sizeof(internalPort), "%u", mDNSVal16(n->IntPort)); |
---|
672 | mDNS_snprintf(publicPortString, sizeof(publicPortString), "iC%u", ReqPortNum); |
---|
673 | mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u", |
---|
674 | m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]); |
---|
675 | |
---|
676 | // build the message |
---|
677 | mDNSPlatformMemZero(propArgs, sizeof(propArgs)); |
---|
678 | propArgs[0].name = "NewRemoteHost"; |
---|
679 | propArgs[0].type = "string"; |
---|
680 | propArgs[0].value = ""; |
---|
681 | propArgs[1].name = "NewExternalPort"; |
---|
682 | propArgs[1].type = "ui2"; |
---|
683 | propArgs[1].value = externalPort; |
---|
684 | propArgs[2].name = "NewProtocol"; |
---|
685 | propArgs[2].type = "string"; |
---|
686 | propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP"; |
---|
687 | propArgs[3].name = "NewInternalPort"; |
---|
688 | propArgs[3].type = "ui2"; |
---|
689 | propArgs[3].value = internalPort; |
---|
690 | propArgs[4].name = "NewInternalClient"; |
---|
691 | propArgs[4].type = "string"; |
---|
692 | propArgs[4].value = localIPAddrString; |
---|
693 | propArgs[5].name = "NewEnabled"; |
---|
694 | propArgs[5].type = "boolean"; |
---|
695 | propArgs[5].value = "1"; |
---|
696 | propArgs[6].name = "NewPortMappingDescription"; |
---|
697 | propArgs[6].type = "string"; |
---|
698 | propArgs[6].value = publicPortString; |
---|
699 | propArgs[7].name = "NewLeaseDuration"; |
---|
700 | propArgs[7].type = "ui4"; |
---|
701 | propArgs[7].value = "0"; |
---|
702 | |
---|
703 | LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum); |
---|
704 | return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp); |
---|
705 | } |
---|
706 | |
---|
707 | mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *const n) |
---|
708 | { |
---|
709 | LogInfo("LNT_MapPort"); |
---|
710 | if (n->tcpInfo.sock) return(mStatus_NoError); // If we already have a connection up don't make another request for the same thing |
---|
711 | n->tcpInfo.parentNATInfo = n; |
---|
712 | n->tcpInfo.retries = 0; |
---|
713 | return SendPortMapRequest(m, n); |
---|
714 | } |
---|
715 | |
---|
716 | mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *const n) |
---|
717 | { |
---|
718 | char externalPort[10]; |
---|
719 | Property propArgs[3]; |
---|
720 | tcpLNTInfo *info; |
---|
721 | tcpLNTInfo **infoPtr = &m->tcpInfoUnmapList; |
---|
722 | mStatus err; |
---|
723 | |
---|
724 | // If no NAT gateway to talk to, no need to do all this work for nothing |
---|
725 | if (mDNSIPPortIsZero(m->UPnPSOAPPort) || !m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError; |
---|
726 | |
---|
727 | mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort)); |
---|
728 | |
---|
729 | mDNSPlatformMemZero(propArgs, sizeof(propArgs)); |
---|
730 | propArgs[0].name = "NewRemoteHost"; |
---|
731 | propArgs[0].type = "string"; |
---|
732 | propArgs[0].value = ""; |
---|
733 | propArgs[1].name = "NewExternalPort"; |
---|
734 | propArgs[1].type = "ui2"; |
---|
735 | propArgs[1].value = externalPort; |
---|
736 | propArgs[2].name = "NewProtocol"; |
---|
737 | propArgs[2].type = "string"; |
---|
738 | propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP"; |
---|
739 | |
---|
740 | n->tcpInfo.parentNATInfo = n; |
---|
741 | |
---|
742 | // clean up previous port mapping requests and allocations |
---|
743 | if (n->tcpInfo.sock) LogInfo("LNT_UnmapPort: closing previous open connection"); |
---|
744 | if (n->tcpInfo.sock ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock = mDNSNULL; } |
---|
745 | if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request); n->tcpInfo.Request = mDNSNULL; } |
---|
746 | if (n->tcpInfo.Reply ) { mDNSPlatformMemFree(n->tcpInfo.Reply); n->tcpInfo.Reply = mDNSNULL; } |
---|
747 | |
---|
748 | // make a copy of the tcpInfo that we can clean up later (the one passed in will be destroyed by the client as soon as this returns) |
---|
749 | if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL) |
---|
750 | { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); } |
---|
751 | *info = n->tcpInfo; |
---|
752 | |
---|
753 | while (*infoPtr) infoPtr = &(*infoPtr)->next; // find the end of the list |
---|
754 | *infoPtr = info; // append |
---|
755 | |
---|
756 | err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp); |
---|
757 | if (err) DisposeInfoFromUnmapList(m, info); |
---|
758 | return err; |
---|
759 | } |
---|
760 | |
---|
761 | mDNSexport mStatus LNT_GetExternalAddress(mDNS *m) |
---|
762 | { |
---|
763 | return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp); |
---|
764 | } |
---|
765 | |
---|
766 | mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info) |
---|
767 | { |
---|
768 | // Device description format - |
---|
769 | // - device description URL |
---|
770 | // - host/port |
---|
771 | static const char szSSDPMsgDescribeDeviceFMT[] = |
---|
772 | "GET %s HTTP/1.1\r\n" |
---|
773 | "Accept: text/xml, application/xml\r\n" |
---|
774 | "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n" |
---|
775 | "Host: %s\r\n" |
---|
776 | "Connection: close\r\n" |
---|
777 | "\r\n"; |
---|
778 | |
---|
779 | if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return mStatus_NoError; // already have the info we need |
---|
780 | |
---|
781 | if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); } |
---|
782 | |
---|
783 | // build message |
---|
784 | if (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer |
---|
785 | else if ((info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); } |
---|
786 | info->requestLen = mDNS_snprintf((char*)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString); |
---|
787 | LogInfo("Describe Device: [%s]", info->Request); |
---|
788 | return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp); |
---|
789 | } |
---|
790 | |
---|
791 | // This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response |
---|
792 | // referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and |
---|
793 | // URL info we need. |
---|
794 | mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, const mDNSu8 *const data, const mDNSu16 len) |
---|
795 | { |
---|
796 | const mDNSu8 *ptr = data; |
---|
797 | const mDNSu8 *end = data + len; |
---|
798 | const mDNSu8 *stop; |
---|
799 | |
---|
800 | if (!mDNSIPPortIsZero(m->UPnPRouterPort)) return; // already have the info we need |
---|
801 | |
---|
802 | // The formatting of the HTTP header is not always the same when it comes to the placement of |
---|
803 | // the service and location strings, so we just look for each of them from the beginning for every response |
---|
804 | |
---|
805 | // figure out if this is a message from a service we care about |
---|
806 | while (ptr && ptr != end) |
---|
807 | { |
---|
808 | if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANIPConnection:1", 17) == 0)) break; |
---|
809 | ptr++; |
---|
810 | } |
---|
811 | if (ptr == end) |
---|
812 | { |
---|
813 | ptr = data; |
---|
814 | while (ptr && ptr != end) |
---|
815 | { |
---|
816 | if ((*ptr & 0xDF) == 'W' && (strncasecmp((char*)ptr, "WANPPPConnection:1", 18) == 0)) break; |
---|
817 | ptr++; |
---|
818 | } |
---|
819 | } |
---|
820 | if (ptr == mDNSNULL || ptr == end) return; // not a message we care about |
---|
821 | |
---|
822 | // find "Location:", starting from the beginning |
---|
823 | ptr = data; |
---|
824 | while (ptr && ptr != end) |
---|
825 | { |
---|
826 | if ((*ptr & 0xDF) == 'L' && (strncasecmp((char*)ptr, "Location:", 9) == 0)) break; // find the first 'L'; is this Location? if not, keep looking |
---|
827 | ptr++; |
---|
828 | } |
---|
829 | if (ptr == mDNSNULL || ptr == end) |
---|
830 | { |
---|
831 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Location", ""); |
---|
832 | return; // not a message we care about |
---|
833 | } |
---|
834 | ptr += 9; //Skip over 'Location:' |
---|
835 | while (*ptr == ' ' && ptr < end) ptr++; // skip over spaces |
---|
836 | if (ptr >= end) return; |
---|
837 | |
---|
838 | // find the end of the line |
---|
839 | for (stop = ptr; stop != end; stop++) { if (*stop == '\r') { end = stop; break; } } |
---|
840 | |
---|
841 | // fill in default port |
---|
842 | m->UPnPRouterPort = mDNSOpaque16fromIntVal(80); |
---|
843 | |
---|
844 | // free string pointers and set to NULL |
---|
845 | if (m->UPnPRouterAddressString != mDNSNULL) |
---|
846 | { |
---|
847 | mDNSPlatformMemFree(m->UPnPRouterAddressString); |
---|
848 | m->UPnPRouterAddressString = mDNSNULL; |
---|
849 | } |
---|
850 | if (m->UPnPRouterURL != mDNSNULL) |
---|
851 | { |
---|
852 | mDNSPlatformMemFree(m->UPnPRouterURL); |
---|
853 | m->UPnPRouterURL = mDNSNULL; |
---|
854 | } |
---|
855 | |
---|
856 | // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc" |
---|
857 | if (ParseHttpUrl(ptr, end, &m->UPnPRouterAddressString, &m->UPnPRouterPort, &m->UPnPRouterURL) != mStatus_NoError) |
---|
858 | { |
---|
859 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Parse URL", ""); |
---|
860 | return; |
---|
861 | } |
---|
862 | |
---|
863 | m->UPnPInterfaceID = InterfaceID; |
---|
864 | |
---|
865 | if (m->UPnPRouterAddressString == mDNSNULL) |
---|
866 | { |
---|
867 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router address", ""); |
---|
868 | LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL"); |
---|
869 | } |
---|
870 | else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m->UPnPRouterAddressString); |
---|
871 | |
---|
872 | if (m->UPnPRouterURL == mDNSNULL) |
---|
873 | { |
---|
874 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router path", ""); |
---|
875 | LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL"); |
---|
876 | } |
---|
877 | else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m->UPnPRouterURL); |
---|
878 | |
---|
879 | LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m->UPnPRouterPort)); |
---|
880 | LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m->UPnPInterfaceID); |
---|
881 | |
---|
882 | // Don't need the SSDP socket anymore |
---|
883 | if (m->SSDPSocket) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; } |
---|
884 | |
---|
885 | mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "success", "success", ""); |
---|
886 | // now send message to get the device description |
---|
887 | GetDeviceDescription(m, &m->tcpDeviceInfo); |
---|
888 | } |
---|
889 | |
---|
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 *buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty |
---|
901 | unsigned int bufLen; |
---|
902 | |
---|
903 | if (!mDNSIPPortIsZero(m->UPnPRouterPort)) |
---|
904 | { |
---|
905 | if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; } |
---|
906 | if (mDNSIPPortIsZero(m->UPnPSOAPPort) && !m->tcpDeviceInfo.sock) GetDeviceDescription(m, &m->tcpDeviceInfo); |
---|
907 | return; |
---|
908 | } |
---|
909 | |
---|
910 | // Always query for WANIPConnection in the first SSDP packet |
---|
911 | if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse; |
---|
912 | |
---|
913 | // Create message |
---|
914 | bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP"); |
---|
915 | |
---|
916 | debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExtAddress); |
---|
917 | |
---|
918 | if (!mDNSIPv4AddressIsZero(m->Router.ip.v4)) |
---|
919 | { |
---|
920 | if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(m, zeroIPPort); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); } |
---|
921 | mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router, SSDPPort, mDNSfalse); |
---|
922 | mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort, mDNSfalse); |
---|
923 | } |
---|
924 | |
---|
925 | m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection; |
---|
926 | } |
---|
927 | |
---|
928 | mDNSexport void LNT_ClearState(mDNS *const m) |
---|
929 | { |
---|
930 | if (m->tcpAddrInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpAddrInfo.sock); m->tcpAddrInfo.sock = mDNSNULL; } |
---|
931 | if (m->tcpDeviceInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpDeviceInfo.sock); m->tcpDeviceInfo.sock = mDNSNULL; } |
---|
932 | m->UPnPSOAPPort = m->UPnPRouterPort = zeroIPPort; // Reset UPnP ports |
---|
933 | } |
---|
934 | |
---|
935 | #endif /* _LEGACY_NAT_TRAVERSAL_ */ |
---|