/src/mozilla-central/media/mtransport/test_nr_socket.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | /* |
7 | | */ |
8 | | |
9 | | /* |
10 | | Based partially on original code from nICEr and nrappkit. |
11 | | |
12 | | nICEr copyright: |
13 | | |
14 | | Copyright (c) 2007, Adobe Systems, Incorporated |
15 | | All rights reserved. |
16 | | |
17 | | Redistribution and use in source and binary forms, with or without |
18 | | modification, are permitted provided that the following conditions are |
19 | | met: |
20 | | |
21 | | * Redistributions of source code must retain the above copyright |
22 | | notice, this list of conditions and the following disclaimer. |
23 | | |
24 | | * Redistributions in binary form must reproduce the above copyright |
25 | | notice, this list of conditions and the following disclaimer in the |
26 | | documentation and/or other materials provided with the distribution. |
27 | | |
28 | | * Neither the name of Adobe Systems, Network Resonance nor the names of its |
29 | | contributors may be used to endorse or promote products derived from |
30 | | this software without specific prior written permission. |
31 | | |
32 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
33 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
34 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
35 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
36 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
37 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
38 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
39 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
40 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
41 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
42 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
43 | | |
44 | | |
45 | | nrappkit copyright: |
46 | | |
47 | | Copyright (C) 2001-2003, Network Resonance, Inc. |
48 | | Copyright (C) 2006, Network Resonance, Inc. |
49 | | All Rights Reserved |
50 | | |
51 | | Redistribution and use in source and binary forms, with or without |
52 | | modification, are permitted provided that the following conditions |
53 | | are met: |
54 | | |
55 | | 1. Redistributions of source code must retain the above copyright |
56 | | notice, this list of conditions and the following disclaimer. |
57 | | 2. Redistributions in binary form must reproduce the above copyright |
58 | | notice, this list of conditions and the following disclaimer in the |
59 | | documentation and/or other materials provided with the distribution. |
60 | | 3. Neither the name of Network Resonance, Inc. nor the name of any |
61 | | contributors to this software may be used to endorse or promote |
62 | | products derived from this software without specific prior written |
63 | | permission. |
64 | | |
65 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' |
66 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
67 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
68 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
69 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
70 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
71 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
72 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
73 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
74 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
75 | | POSSIBILITY OF SUCH DAMAGE. |
76 | | |
77 | | |
78 | | ekr@rtfm.com Thu Dec 20 20:14:49 2001 |
79 | | */ |
80 | | |
81 | | // Original author: bcampen@mozilla.com [:bwc] |
82 | | |
83 | | extern "C" { |
84 | | #include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE |
85 | | #include "nr_api.h" |
86 | | #include "async_wait.h" |
87 | | #include "async_timer.h" |
88 | | #include "nr_socket.h" |
89 | | #include "nr_socket_local.h" |
90 | | #include "stun_hint.h" |
91 | | #include "transport_addr.h" |
92 | | } |
93 | | |
94 | | #include "mozilla/RefPtr.h" |
95 | | #include "test_nr_socket.h" |
96 | | #include "runnable_utils.h" |
97 | | |
98 | | namespace mozilla { |
99 | | |
100 | | static int test_nat_socket_create(void *obj, |
101 | | nr_transport_addr *addr, |
102 | 0 | nr_socket **sockp) { |
103 | 0 | RefPtr<NrSocketBase> sock = new TestNrSocket(static_cast<TestNat*>(obj)); |
104 | 0 |
|
105 | 0 | int r, _status; |
106 | 0 |
|
107 | 0 | r = sock->create(addr); |
108 | 0 | if (r) |
109 | 0 | ABORT(r); |
110 | 0 |
|
111 | 0 | r = nr_socket_create_int(static_cast<void *>(sock), |
112 | 0 | sock->vtbl(), sockp); |
113 | 0 | if (r) |
114 | 0 | ABORT(r); |
115 | 0 |
|
116 | 0 | _status = 0; |
117 | 0 |
|
118 | 0 | { |
119 | 0 | // We will release this reference in destroy(), not exactly the normal |
120 | 0 | // ownership model, but it is what it is. |
121 | 0 | NrSocketBase *dummy = sock.forget().take(); |
122 | 0 | (void)dummy; |
123 | 0 | } |
124 | 0 |
|
125 | 0 | abort: |
126 | 0 | return _status; |
127 | 0 | } |
128 | | |
129 | 0 | static int test_nat_socket_factory_destroy(void **obj) { |
130 | 0 | TestNat *nat = static_cast<TestNat*>(*obj); |
131 | 0 | *obj = nullptr; |
132 | 0 | nat->Release(); |
133 | 0 | return 0; |
134 | 0 | } |
135 | | |
136 | | static nr_socket_factory_vtbl test_nat_socket_factory_vtbl = { |
137 | | test_nat_socket_create, |
138 | | test_nat_socket_factory_destroy |
139 | | }; |
140 | | |
141 | | /* static */ |
142 | | TestNat::NatBehavior |
143 | 0 | TestNat::ToNatBehavior(const std::string& type) { |
144 | 0 | if (!type.compare("ENDPOINT_INDEPENDENT")) { |
145 | 0 | return TestNat::ENDPOINT_INDEPENDENT; |
146 | 0 | } |
147 | 0 | if (!type.compare("ADDRESS_DEPENDENT")) { |
148 | 0 | return TestNat::ADDRESS_DEPENDENT; |
149 | 0 | } |
150 | 0 | if (!type.compare("PORT_DEPENDENT")) { |
151 | 0 | return TestNat::PORT_DEPENDENT; |
152 | 0 | } |
153 | 0 | |
154 | 0 | MOZ_ASSERT(false, "Invalid NAT behavior"); |
155 | 0 | return TestNat::ENDPOINT_INDEPENDENT; |
156 | 0 | } |
157 | | |
158 | 0 | bool TestNat::has_port_mappings() const { |
159 | 0 | for (TestNrSocket *sock : sockets_) { |
160 | 0 | if (sock->has_port_mappings()) { |
161 | 0 | return true; |
162 | 0 | } |
163 | 0 | } |
164 | 0 | return false; |
165 | 0 | } |
166 | | |
167 | 0 | bool TestNat::is_my_external_tuple(const nr_transport_addr &addr) const { |
168 | 0 | for (TestNrSocket *sock : sockets_) { |
169 | 0 | if (sock->is_my_external_tuple(addr)) { |
170 | 0 | return true; |
171 | 0 | } |
172 | 0 | } |
173 | 0 |
|
174 | 0 | return false; |
175 | 0 | } |
176 | | |
177 | 0 | bool TestNat::is_an_internal_tuple(const nr_transport_addr &addr) const { |
178 | 0 | for (TestNrSocket *sock : sockets_) { |
179 | 0 | nr_transport_addr addr_behind_nat; |
180 | 0 | if (sock->getaddr(&addr_behind_nat)) { |
181 | 0 | MOZ_CRASH("TestNrSocket::getaddr failed!"); |
182 | 0 | } |
183 | 0 |
|
184 | 0 | // TODO(bug 1170299): Remove const_cast when no longer necessary |
185 | 0 | if (!nr_transport_addr_cmp(const_cast<nr_transport_addr*>(&addr), |
186 | 0 | &addr_behind_nat, |
187 | 0 | NR_TRANSPORT_ADDR_CMP_MODE_ALL)) { |
188 | 0 | return true; |
189 | 0 | } |
190 | 0 | } |
191 | 0 | return false; |
192 | 0 | } |
193 | | |
194 | 0 | int TestNat::create_socket_factory(nr_socket_factory **factorypp) { |
195 | 0 | int r = nr_socket_factory_create_int(this, |
196 | 0 | &test_nat_socket_factory_vtbl, |
197 | 0 | factorypp); |
198 | 0 | if (!r) { |
199 | 0 | AddRef(); |
200 | 0 | } |
201 | 0 | return r; |
202 | 0 | } |
203 | | |
204 | | TestNrSocket::TestNrSocket(TestNat *nat) |
205 | | : nat_(nat), |
206 | | tls_(false), |
207 | 0 | timer_handle_(nullptr) { |
208 | 0 | nat_->insert_socket(this); |
209 | 0 | } |
210 | | |
211 | 0 | TestNrSocket::~TestNrSocket() { |
212 | 0 | nat_->erase_socket(this); |
213 | 0 | } |
214 | | |
215 | | RefPtr<NrSocketBase> TestNrSocket::create_external_socket( |
216 | 0 | const nr_transport_addr &dest_addr) const { |
217 | 0 | MOZ_ASSERT(nat_->enabled_); |
218 | 0 | MOZ_ASSERT(!nat_->is_an_internal_tuple(dest_addr)); |
219 | 0 |
|
220 | 0 | int r; |
221 | 0 | nr_transport_addr nat_external_addr; |
222 | 0 |
|
223 | 0 | // Open the socket on an arbitrary port, on the same address. |
224 | 0 | // TODO(bug 1170299): Remove const_cast when no longer necessary |
225 | 0 | if ((r = nr_transport_addr_copy( |
226 | 0 | &nat_external_addr, |
227 | 0 | const_cast<nr_transport_addr*>(&internal_socket_->my_addr())))) { |
228 | 0 | r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_copy: %d", |
229 | 0 | __FUNCTION__, r); |
230 | 0 | return nullptr; |
231 | 0 | } |
232 | 0 |
|
233 | 0 | if ((r = nr_transport_addr_set_port(&nat_external_addr, 0))) { |
234 | 0 | r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_set_port: %d", |
235 | 0 | __FUNCTION__, r); |
236 | 0 | return nullptr; |
237 | 0 | } |
238 | 0 |
|
239 | 0 | RefPtr<NrSocketBase> external_socket; |
240 | 0 | r = NrSocketBase::CreateSocket(&nat_external_addr, &external_socket); |
241 | 0 |
|
242 | 0 | if (r) { |
243 | 0 | r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in NrSocket::create: %d", |
244 | 0 | __FUNCTION__, r); |
245 | 0 | return nullptr; |
246 | 0 | } |
247 | 0 |
|
248 | 0 | return external_socket; |
249 | 0 | } |
250 | | |
251 | 0 | int TestNrSocket::create(nr_transport_addr *addr) { |
252 | 0 | if (addr->tls_host[0] != '\0') { |
253 | 0 | tls_ = true; |
254 | 0 | } |
255 | 0 |
|
256 | 0 | return NrSocketBase::CreateSocket(addr, &internal_socket_); |
257 | 0 | } |
258 | | |
259 | 0 | int TestNrSocket::getaddr(nr_transport_addr *addrp) { |
260 | 0 | return internal_socket_->getaddr(addrp); |
261 | 0 | } |
262 | | |
263 | 0 | void TestNrSocket::close() { |
264 | 0 | if (timer_handle_) { |
265 | 0 | NR_async_timer_cancel(timer_handle_); |
266 | 0 | timer_handle_ = nullptr; |
267 | 0 | } |
268 | 0 | internal_socket_->close(); |
269 | 0 | for (RefPtr<PortMapping>& port_mapping : port_mappings_) { |
270 | 0 | port_mapping->external_socket_->close(); |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | 0 | int TestNrSocket::listen(int backlog) { |
275 | 0 | MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP); |
276 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
277 | 0 | "TestNrSocket %s listening", |
278 | 0 | internal_socket_->my_addr().as_string); |
279 | 0 |
|
280 | 0 | return internal_socket_->listen(backlog); |
281 | 0 | } |
282 | | |
283 | 0 | int TestNrSocket::accept(nr_transport_addr *addrp, nr_socket **sockp) { |
284 | 0 | MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP); |
285 | 0 | int r = internal_socket_->accept(addrp, sockp); |
286 | 0 | if (r) { |
287 | 0 | return r; |
288 | 0 | } |
289 | 0 | |
290 | 0 | if (nat_->enabled_ && !nat_->is_an_internal_tuple(*addrp)) { |
291 | 0 | nr_socket_destroy(sockp); |
292 | 0 | return R_IO_ERROR; |
293 | 0 | } |
294 | 0 |
|
295 | 0 | return 0; |
296 | 0 | } |
297 | | |
298 | 0 | void TestNrSocket::process_delayed_cb(NR_SOCKET s, int how, void *cb_arg) { |
299 | 0 | DeferredPacket *op = static_cast<DeferredPacket *>(cb_arg); |
300 | 0 | op->socket_->timer_handle_ = nullptr; |
301 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
302 | 0 | "TestNrSocket %s sending delayed STUN response", |
303 | 0 | op->internal_socket_->my_addr().as_string); |
304 | 0 | op->internal_socket_->sendto(op->buffer_.data(), op->buffer_.len(), |
305 | 0 | op->flags_, &op->to_); |
306 | 0 |
|
307 | 0 | delete op; |
308 | 0 | } |
309 | | |
310 | | int TestNrSocket::sendto(const void *msg, size_t len, |
311 | 0 | int flags, nr_transport_addr *to) { |
312 | 0 | MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP); |
313 | 0 |
|
314 | 0 | if (nat_->nat_delegate_ && nat_->nat_delegate_->on_sendto(nat_, msg, len, flags, to)) { |
315 | 0 | return 0; |
316 | 0 | } |
317 | 0 | |
318 | 0 | UCHAR *buf = static_cast<UCHAR*>(const_cast<void*>(msg)); |
319 | 0 | if (nat_->block_stun_ && |
320 | 0 | nr_is_stun_message(buf, len)) { |
321 | 0 | return 0; |
322 | 0 | } |
323 | 0 | |
324 | 0 | /* TODO: improve the functionality of this in bug 1253657 */ |
325 | 0 | if (!nat_->enabled_ || nat_->is_an_internal_tuple(*to)) { |
326 | 0 | if (nat_->delay_stun_resp_ms_ && |
327 | 0 | nr_is_stun_response_message(buf, len)) { |
328 | 0 | NR_ASYNC_TIMER_SET(nat_->delay_stun_resp_ms_, |
329 | 0 | process_delayed_cb, |
330 | 0 | new DeferredPacket(this, msg, len, flags, to, |
331 | 0 | internal_socket_), |
332 | 0 | &timer_handle_); |
333 | 0 | return 0; |
334 | 0 | } |
335 | 0 | return internal_socket_->sendto(msg, len, flags, to); |
336 | 0 | } |
337 | 0 | |
338 | 0 | destroy_stale_port_mappings(); |
339 | 0 |
|
340 | 0 | if (to->protocol == IPPROTO_UDP && nat_->block_udp_) { |
341 | 0 | // Silently eat the packet |
342 | 0 | return 0; |
343 | 0 | } |
344 | 0 | |
345 | 0 | // Choose our port mapping based on our most selective criteria |
346 | 0 | PortMapping *port_mapping = get_port_mapping(*to, |
347 | 0 | std::max(nat_->filtering_type_, |
348 | 0 | nat_->mapping_type_)); |
349 | 0 |
|
350 | 0 | if (!port_mapping) { |
351 | 0 | // See if we have already made the external socket we need to use. |
352 | 0 | PortMapping *similar_port_mapping = |
353 | 0 | get_port_mapping(*to, nat_->mapping_type_); |
354 | 0 | RefPtr<NrSocketBase> external_socket; |
355 | 0 |
|
356 | 0 | if (similar_port_mapping) { |
357 | 0 | external_socket = similar_port_mapping->external_socket_; |
358 | 0 | } else { |
359 | 0 | external_socket = create_external_socket(*to); |
360 | 0 | if (!external_socket) { |
361 | 0 | MOZ_ASSERT(false); |
362 | 0 | return R_INTERNAL; |
363 | 0 | } |
364 | 0 | } |
365 | 0 |
|
366 | 0 | port_mapping = create_port_mapping(*to, external_socket); |
367 | 0 | port_mappings_.push_back(port_mapping); |
368 | 0 |
|
369 | 0 | if (poll_flags() & PR_POLL_READ) { |
370 | 0 | // Make sure the new port mapping is ready to receive traffic if the |
371 | 0 | // TestNrSocket is already waiting. |
372 | 0 | port_mapping->async_wait(NR_ASYNC_WAIT_READ, |
373 | 0 | socket_readable_callback, |
374 | 0 | this, |
375 | 0 | (char*)__FUNCTION__, |
376 | 0 | __LINE__); |
377 | 0 | } |
378 | 0 | } |
379 | 0 |
|
380 | 0 | // We probably don't want to propagate the flags, since this is a simulated |
381 | 0 | // external IP address. |
382 | 0 | return port_mapping->sendto(msg, len, *to); |
383 | 0 | } |
384 | | |
385 | | int TestNrSocket::recvfrom(void *buf, size_t maxlen, |
386 | | size_t *len, int flags, |
387 | 0 | nr_transport_addr *from) { |
388 | 0 | MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP); |
389 | 0 |
|
390 | 0 | int r; |
391 | 0 | bool ingress_allowed = false; |
392 | 0 |
|
393 | 0 | if (readable_socket_) { |
394 | 0 | // If any of the external sockets got data, see if it will be passed through |
395 | 0 | r = readable_socket_->recvfrom(buf, maxlen, len, 0, from); |
396 | 0 | readable_socket_ = nullptr; |
397 | 0 | if (!r) { |
398 | 0 | PortMapping *port_mapping_used; |
399 | 0 | ingress_allowed = allow_ingress(*from, &port_mapping_used); |
400 | 0 | if (ingress_allowed) { |
401 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s via %s", |
402 | 0 | internal_socket_->my_addr().as_string, |
403 | 0 | from->as_string, |
404 | 0 | port_mapping_used->external_socket_->my_addr().as_string); |
405 | 0 | if (nat_->refresh_on_ingress_) { |
406 | 0 | port_mapping_used->last_used_ = PR_IntervalNow(); |
407 | 0 | } |
408 | 0 | } |
409 | 0 | } |
410 | 0 | } else { |
411 | 0 | // If no external socket has data, see if there's any data that was sent |
412 | 0 | // directly to the TestNrSocket, and eat it if it isn't supposed to get |
413 | 0 | // through. |
414 | 0 | r = internal_socket_->recvfrom(buf, maxlen, len, flags, from); |
415 | 0 | if (!r) { |
416 | 0 | // We do not use allow_ingress() here because that only handles traffic |
417 | 0 | // landing on an external port. |
418 | 0 | ingress_allowed = (!nat_->enabled_ || |
419 | 0 | nat_->is_an_internal_tuple(*from)); |
420 | 0 | if (!ingress_allowed) { |
421 | 0 | r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " |
422 | 0 | "Not behind the same NAT", |
423 | 0 | internal_socket_->my_addr().as_string, |
424 | 0 | from->as_string); |
425 | 0 | } else { |
426 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s", |
427 | 0 | internal_socket_->my_addr().as_string, |
428 | 0 | from->as_string); |
429 | 0 | } |
430 | 0 | } |
431 | 0 | } |
432 | 0 |
|
433 | 0 | // Kinda lame that we are forced to give the app a readable callback and then |
434 | 0 | // say "Oh, never mind...", but the alternative is to totally decouple the |
435 | 0 | // callbacks from STS and the callbacks the app sets. On the bright side, this |
436 | 0 | // speeds up unit tests where we are verifying that ingress is forbidden, |
437 | 0 | // since they'll get a readable callback and then an error, instead of having |
438 | 0 | // to wait for a timeout. |
439 | 0 | if (!ingress_allowed) { |
440 | 0 | *len = 0; |
441 | 0 | r = R_WOULDBLOCK; |
442 | 0 | } |
443 | 0 |
|
444 | 0 | return r; |
445 | 0 | } |
446 | | |
447 | | bool TestNrSocket::allow_ingress(const nr_transport_addr &from, |
448 | 0 | PortMapping **port_mapping_used) const { |
449 | 0 | // This is only called for traffic arriving at a port mapping |
450 | 0 | MOZ_ASSERT(nat_->enabled_); |
451 | 0 | MOZ_ASSERT(!nat_->is_an_internal_tuple(from)); |
452 | 0 |
|
453 | 0 | *port_mapping_used = get_port_mapping(from, nat_->filtering_type_); |
454 | 0 | if (!(*port_mapping_used)) { |
455 | 0 | r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " |
456 | 0 | "Filtered", |
457 | 0 | internal_socket_->my_addr().as_string, |
458 | 0 | from.as_string); |
459 | 0 | return false; |
460 | 0 | } |
461 | 0 |
|
462 | 0 | if (is_port_mapping_stale(**port_mapping_used)) { |
463 | 0 | r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " |
464 | 0 | "Stale port mapping", |
465 | 0 | internal_socket_->my_addr().as_string, |
466 | 0 | from.as_string); |
467 | 0 | return false; |
468 | 0 | } |
469 | 0 |
|
470 | 0 | if (!nat_->allow_hairpinning_ && nat_->is_my_external_tuple(from)) { |
471 | 0 | r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " |
472 | 0 | "Hairpinning disallowed", |
473 | 0 | internal_socket_->my_addr().as_string, |
474 | 0 | from.as_string); |
475 | 0 | return false; |
476 | 0 | } |
477 | 0 |
|
478 | 0 | return true; |
479 | 0 | } |
480 | | |
481 | 0 | int TestNrSocket::connect(nr_transport_addr *addr) { |
482 | 0 |
|
483 | 0 | if (connect_invoked_ || !port_mappings_.empty()) { |
484 | 0 | MOZ_CRASH("TestNrSocket::connect() called more than once!"); |
485 | 0 | return R_INTERNAL; |
486 | 0 | } |
487 | 0 | |
488 | 0 | if (!nat_->enabled_ |
489 | 0 | || addr->protocol==IPPROTO_UDP // Horrible hack to allow default address |
490 | 0 | // discovery to work. Only works because |
491 | 0 | // we don't normally connect on UDP. |
492 | 0 | || nat_->is_an_internal_tuple(*addr)) { |
493 | 0 | // This will set connect_invoked_ |
494 | 0 | return internal_socket_->connect(addr); |
495 | 0 | } |
496 | 0 | |
497 | 0 | RefPtr<NrSocketBase> external_socket(create_external_socket(*addr)); |
498 | 0 | if (!external_socket) { |
499 | 0 | return R_INTERNAL; |
500 | 0 | } |
501 | 0 |
|
502 | 0 | PortMapping *port_mapping = create_port_mapping(*addr, external_socket); |
503 | 0 | port_mappings_.push_back(port_mapping); |
504 | 0 | int r = port_mapping->external_socket_->connect(addr); |
505 | 0 | if (r && r != R_WOULDBLOCK) { |
506 | 0 | return r; |
507 | 0 | } |
508 | 0 | |
509 | 0 | port_mapping->last_used_ = PR_IntervalNow(); |
510 | 0 |
|
511 | 0 | if (poll_flags() & PR_POLL_READ) { |
512 | 0 | port_mapping->async_wait(NR_ASYNC_WAIT_READ, |
513 | 0 | port_mapping_tcp_passthrough_callback, |
514 | 0 | this, |
515 | 0 | (char*)__FUNCTION__, |
516 | 0 | __LINE__); |
517 | 0 | } |
518 | 0 |
|
519 | 0 | return r; |
520 | 0 | } |
521 | | |
522 | 0 | int TestNrSocket::write(const void *msg, size_t len, size_t *written) { |
523 | 0 | UCHAR *buf = static_cast<UCHAR*>(const_cast<void*>(msg)); |
524 | 0 |
|
525 | 0 | if (nat_->nat_delegate_ && nat_->nat_delegate_->on_write(nat_, msg, len, written)) { |
526 | 0 | return R_INTERNAL; |
527 | 0 | } |
528 | 0 |
|
529 | 0 | if (nat_->block_stun_ && nr_is_stun_message(buf, len)) { |
530 | 0 | // Should cause this socket to be abandoned |
531 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
532 | 0 | "TestNrSocket %s dropping outgoing TCP " |
533 | 0 | "because it is configured to drop STUN", |
534 | 0 | my_addr().as_string); |
535 | 0 | return R_INTERNAL; |
536 | 0 | } |
537 | 0 |
|
538 | 0 | if (nat_->block_tcp_ && !tls_) { |
539 | 0 | // Should cause this socket to be abandoned |
540 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
541 | 0 | "TestNrSocket %s dropping outgoing TCP " |
542 | 0 | "because it is configured to drop TCP", |
543 | 0 | my_addr().as_string); |
544 | 0 | return R_INTERNAL; |
545 | 0 | } |
546 | 0 |
|
547 | 0 | if (port_mappings_.empty()) { |
548 | 0 | // The no-nat case, just pass call through. |
549 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s writing", |
550 | 0 | my_addr().as_string); |
551 | 0 |
|
552 | 0 | return internal_socket_->write(msg, len, written); |
553 | 0 | } |
554 | 0 | destroy_stale_port_mappings(); |
555 | 0 | if (port_mappings_.empty()) { |
556 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
557 | 0 | "TestNrSocket %s dropping outgoing TCP " |
558 | 0 | "because the port mapping was stale", |
559 | 0 | my_addr().as_string); |
560 | 0 | return R_INTERNAL; |
561 | 0 | } |
562 | 0 | // This is TCP only |
563 | 0 | MOZ_ASSERT(port_mappings_.size() == 1); |
564 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
565 | 0 | "PortMapping %s -> %s writing", |
566 | 0 | port_mappings_.front()->external_socket_->my_addr().as_string, |
567 | 0 | port_mappings_.front()->remote_address_.as_string); |
568 | 0 | port_mappings_.front()->last_used_ = PR_IntervalNow(); |
569 | 0 | return port_mappings_.front()->external_socket_->write(msg, len, written); |
570 | 0 | } |
571 | | |
572 | 0 | int TestNrSocket::read(void *buf, size_t maxlen, size_t *len) { |
573 | 0 | int r; |
574 | 0 |
|
575 | 0 | if (port_mappings_.empty()) { |
576 | 0 | r = internal_socket_->read(buf, maxlen, len); |
577 | 0 | } else { |
578 | 0 | MOZ_ASSERT(port_mappings_.size() == 1); |
579 | 0 | r = port_mappings_.front()->external_socket_->read(buf, maxlen, len); |
580 | 0 | if (!r && nat_->refresh_on_ingress_) { |
581 | 0 | port_mappings_.front()->last_used_ = PR_IntervalNow(); |
582 | 0 | } |
583 | 0 | } |
584 | 0 |
|
585 | 0 | if (r) { |
586 | 0 | return r; |
587 | 0 | } |
588 | 0 | |
589 | 0 | if (nat_->nat_delegate_ && nat_->nat_delegate_->on_read(nat_, buf, maxlen, len)) { |
590 | 0 | return R_INTERNAL; |
591 | 0 | } |
592 | 0 |
|
593 | 0 | if (nat_->block_tcp_ && !tls_) { |
594 | 0 | // Should cause this socket to be abandoned |
595 | 0 | return R_INTERNAL; |
596 | 0 | } |
597 | 0 |
|
598 | 0 | UCHAR *cbuf = static_cast<UCHAR*>(const_cast<void*>(buf)); |
599 | 0 | if (nat_->block_stun_ && nr_is_stun_message(cbuf, *len)) { |
600 | 0 | // Should cause this socket to be abandoned |
601 | 0 | return R_INTERNAL; |
602 | 0 | } |
603 | 0 |
|
604 | 0 | return r; |
605 | 0 | } |
606 | | |
607 | | int TestNrSocket::async_wait(int how, NR_async_cb cb, void *cb_arg, |
608 | 0 | char *function, int line) { |
609 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s waiting for %s", |
610 | 0 | internal_socket_->my_addr().as_string, |
611 | 0 | how == NR_ASYNC_WAIT_READ ? "read" : "write"); |
612 | 0 |
|
613 | 0 | int r; |
614 | 0 |
|
615 | 0 | if (how == NR_ASYNC_WAIT_READ) { |
616 | 0 | NrSocketBase::async_wait(how, cb, cb_arg, function, line); |
617 | 0 |
|
618 | 0 | // Make sure we're waiting on the socket for the internal address |
619 | 0 | r = internal_socket_->async_wait(how, |
620 | 0 | socket_readable_callback, |
621 | 0 | this, |
622 | 0 | function, |
623 | 0 | line); |
624 | 0 | } else { |
625 | 0 | // For write, just use the readiness of the internal socket, since we queue |
626 | 0 | // everything for the port mappings. |
627 | 0 | r = internal_socket_->async_wait(how, |
628 | 0 | cb, |
629 | 0 | cb_arg, |
630 | 0 | function, |
631 | 0 | line); |
632 | 0 | } |
633 | 0 |
|
634 | 0 | if (r) { |
635 | 0 | r_log(LOG_GENERIC, LOG_ERR, "TestNrSocket %s failed to async_wait for " |
636 | 0 | "internal socket: %d\n", |
637 | 0 | internal_socket_->my_addr().as_string, |
638 | 0 | r); |
639 | 0 | return r; |
640 | 0 | } |
641 | 0 |
|
642 | 0 | if (is_tcp_connection_behind_nat()) { |
643 | 0 | // Bypass all port-mapping related logic |
644 | 0 | return 0; |
645 | 0 | } |
646 | 0 | |
647 | 0 | if (internal_socket_->my_addr().protocol == IPPROTO_TCP) { |
648 | 0 | // For a TCP connection through a simulated NAT, these signals are |
649 | 0 | // just passed through. |
650 | 0 | MOZ_ASSERT(port_mappings_.size() == 1); |
651 | 0 |
|
652 | 0 | return port_mappings_.front()->async_wait( |
653 | 0 | how, |
654 | 0 | port_mapping_tcp_passthrough_callback, |
655 | 0 | this, |
656 | 0 | function, |
657 | 0 | line); |
658 | 0 | } |
659 | 0 | if (how == NR_ASYNC_WAIT_READ) { |
660 | 0 | // For UDP port mappings, we decouple the writeable callbacks |
661 | 0 | for (PortMapping *port_mapping : port_mappings_) { |
662 | 0 | // Be ready to receive traffic on our port mappings |
663 | 0 | r = port_mapping->async_wait(how, |
664 | 0 | socket_readable_callback, |
665 | 0 | this, |
666 | 0 | function, |
667 | 0 | line); |
668 | 0 | if (r) { |
669 | 0 | r_log(LOG_GENERIC, LOG_ERR, "TestNrSocket %s failed to async_wait for " |
670 | 0 | "port mapping: %d\n", |
671 | 0 | internal_socket_->my_addr().as_string, |
672 | 0 | r); |
673 | 0 | return r; |
674 | 0 | } |
675 | 0 | } |
676 | 0 | } |
677 | 0 |
|
678 | 0 | return 0; |
679 | 0 | } |
680 | | |
681 | 0 | void TestNrSocket::cancel_port_mapping_async_wait(int how) { |
682 | 0 | for (PortMapping *port_mapping : port_mappings_) { |
683 | 0 | port_mapping->cancel(how); |
684 | 0 | } |
685 | 0 | } |
686 | | |
687 | 0 | int TestNrSocket::cancel(int how) { |
688 | 0 |
|
689 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s stop waiting for %s", |
690 | 0 | internal_socket_->my_addr().as_string, |
691 | 0 | how == NR_ASYNC_WAIT_READ ? "read" : "write"); |
692 | 0 |
|
693 | 0 | // Writable callbacks are decoupled except for the TCP case |
694 | 0 | if (how == NR_ASYNC_WAIT_READ || |
695 | 0 | internal_socket_->my_addr().protocol == IPPROTO_TCP) { |
696 | 0 | cancel_port_mapping_async_wait(how); |
697 | 0 | } |
698 | 0 |
|
699 | 0 | return internal_socket_->cancel(how); |
700 | 0 | } |
701 | | |
702 | 0 | bool TestNrSocket::has_port_mappings() const { |
703 | 0 | return !port_mappings_.empty(); |
704 | 0 | } |
705 | | |
706 | 0 | bool TestNrSocket::is_my_external_tuple(const nr_transport_addr &addr) const { |
707 | 0 | for (PortMapping *port_mapping : port_mappings_) { |
708 | 0 | nr_transport_addr port_mapping_addr; |
709 | 0 | if (port_mapping->external_socket_->getaddr(&port_mapping_addr)) { |
710 | 0 | MOZ_CRASH("NrSocket::getaddr failed!"); |
711 | 0 | } |
712 | 0 |
|
713 | 0 | // TODO(bug 1170299): Remove const_cast when no longer necessary |
714 | 0 | if (!nr_transport_addr_cmp(const_cast<nr_transport_addr*>(&addr), |
715 | 0 | &port_mapping_addr, |
716 | 0 | NR_TRANSPORT_ADDR_CMP_MODE_ALL)) { |
717 | 0 | return true; |
718 | 0 | } |
719 | 0 | } |
720 | 0 | return false; |
721 | 0 | } |
722 | | |
723 | | bool TestNrSocket::is_port_mapping_stale( |
724 | 0 | const PortMapping &port_mapping) const { |
725 | 0 | PRIntervalTime now = PR_IntervalNow(); |
726 | 0 | PRIntervalTime elapsed_ticks = now - port_mapping.last_used_; |
727 | 0 | uint32_t idle_duration = PR_IntervalToMilliseconds(elapsed_ticks); |
728 | 0 | return idle_duration > nat_->mapping_timeout_; |
729 | 0 | } |
730 | | |
731 | 0 | void TestNrSocket::destroy_stale_port_mappings() { |
732 | 0 | for (auto i = port_mappings_.begin(); i != port_mappings_.end();) { |
733 | 0 | auto temp = i; |
734 | 0 | ++i; |
735 | 0 | if (is_port_mapping_stale(**temp)) { |
736 | 0 | r_log(LOG_GENERIC, LOG_INFO, |
737 | 0 | "TestNrSocket %s destroying port mapping %s -> %s", |
738 | 0 | internal_socket_->my_addr().as_string, |
739 | 0 | (*temp)->external_socket_->my_addr().as_string, |
740 | 0 | (*temp)->remote_address_.as_string); |
741 | 0 |
|
742 | 0 | port_mappings_.erase(temp); |
743 | 0 | } |
744 | 0 | } |
745 | 0 | } |
746 | | |
747 | | void TestNrSocket::socket_readable_callback(void *real_sock_v, |
748 | | int how, |
749 | 0 | void *test_sock_v) { |
750 | 0 | TestNrSocket *test_socket = static_cast<TestNrSocket*>(test_sock_v); |
751 | 0 | NrSocketBase *real_socket = static_cast<NrSocketBase*>(real_sock_v); |
752 | 0 |
|
753 | 0 | test_socket->on_socket_readable(real_socket); |
754 | 0 | } |
755 | | |
756 | 0 | void TestNrSocket::on_socket_readable(NrSocketBase *real_socket) { |
757 | 0 | if (!readable_socket_ && (real_socket != internal_socket_)) { |
758 | 0 | readable_socket_ = real_socket; |
759 | 0 | } |
760 | 0 |
|
761 | 0 | fire_readable_callback(); |
762 | 0 | } |
763 | | |
764 | 0 | void TestNrSocket::fire_readable_callback() { |
765 | 0 | MOZ_ASSERT(poll_flags() & PR_POLL_READ); |
766 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s ready for read", |
767 | 0 | internal_socket_->my_addr().as_string); |
768 | 0 | fire_callback(NR_ASYNC_WAIT_READ); |
769 | 0 | } |
770 | | |
771 | | void TestNrSocket::port_mapping_writeable_callback(void *ext_sock_v, |
772 | | int how, |
773 | 0 | void *test_sock_v) { |
774 | 0 | TestNrSocket *test_socket = static_cast<TestNrSocket*>(test_sock_v); |
775 | 0 | NrSocketBase *external_socket = static_cast<NrSocketBase*>(ext_sock_v); |
776 | 0 |
|
777 | 0 | test_socket->write_to_port_mapping(external_socket); |
778 | 0 | } |
779 | | |
780 | 0 | void TestNrSocket::write_to_port_mapping(NrSocketBase *external_socket) { |
781 | 0 | MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP); |
782 | 0 |
|
783 | 0 | int r = 0; |
784 | 0 | for (PortMapping *port_mapping : port_mappings_) { |
785 | 0 | if (port_mapping->external_socket_ == external_socket) { |
786 | 0 | // If the send succeeds, or if there was nothing to send, we keep going |
787 | 0 | r = port_mapping->send_from_queue(); |
788 | 0 | if (r) { |
789 | 0 | break; |
790 | 0 | } |
791 | 0 | } |
792 | 0 | } |
793 | 0 |
|
794 | 0 | if (r == R_WOULDBLOCK) { |
795 | 0 | // Re-register for writeable callbacks, since we still have stuff to send |
796 | 0 | NR_ASYNC_WAIT(external_socket, |
797 | 0 | NR_ASYNC_WAIT_WRITE, |
798 | 0 | &TestNrSocket::port_mapping_writeable_callback, |
799 | 0 | this); |
800 | 0 | } |
801 | 0 | } |
802 | | |
803 | | void TestNrSocket::port_mapping_tcp_passthrough_callback(void *ext_sock_v, |
804 | | int how, |
805 | 0 | void *test_sock_v) { |
806 | 0 | TestNrSocket *test_socket = static_cast<TestNrSocket*>(test_sock_v); |
807 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
808 | 0 | "TestNrSocket %s firing %s callback", |
809 | 0 | test_socket->internal_socket_->my_addr().as_string, |
810 | 0 | how == NR_ASYNC_WAIT_READ ? "readable" : "writeable"); |
811 | 0 |
|
812 | 0 |
|
813 | 0 | test_socket->internal_socket_->fire_callback(how); |
814 | 0 | } |
815 | | |
816 | 0 | bool TestNrSocket::is_tcp_connection_behind_nat() const { |
817 | 0 | return internal_socket_->my_addr().protocol == IPPROTO_TCP && |
818 | 0 | port_mappings_.empty(); |
819 | 0 | } |
820 | | |
821 | | TestNrSocket::PortMapping* TestNrSocket::get_port_mapping( |
822 | | const nr_transport_addr &remote_address, |
823 | 0 | TestNat::NatBehavior filter) const { |
824 | 0 | int compare_flags; |
825 | 0 | switch (filter) { |
826 | 0 | case TestNat::ENDPOINT_INDEPENDENT: |
827 | 0 | compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL; |
828 | 0 | break; |
829 | 0 | case TestNat::ADDRESS_DEPENDENT: |
830 | 0 | compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ADDR; |
831 | 0 | break; |
832 | 0 | case TestNat::PORT_DEPENDENT: |
833 | 0 | compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ALL; |
834 | 0 | break; |
835 | 0 | } |
836 | 0 |
|
837 | 0 | for (PortMapping *port_mapping : port_mappings_) { |
838 | 0 | // TODO(bug 1170299): Remove const_cast when no longer necessary |
839 | 0 | if (!nr_transport_addr_cmp(const_cast<nr_transport_addr*>(&remote_address), |
840 | 0 | &port_mapping->remote_address_, |
841 | 0 | compare_flags)) |
842 | 0 | return port_mapping; |
843 | 0 | } |
844 | 0 | return nullptr; |
845 | 0 | } |
846 | | |
847 | | TestNrSocket::PortMapping* TestNrSocket::create_port_mapping( |
848 | | const nr_transport_addr &remote_address, |
849 | 0 | const RefPtr<NrSocketBase> &external_socket) const { |
850 | 0 | r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s creating port mapping %s -> %s", |
851 | 0 | internal_socket_->my_addr().as_string, |
852 | 0 | external_socket->my_addr().as_string, |
853 | 0 | remote_address.as_string); |
854 | 0 |
|
855 | 0 | return new PortMapping(remote_address, external_socket); |
856 | 0 | } |
857 | | |
858 | | TestNrSocket::PortMapping::PortMapping( |
859 | | const nr_transport_addr &remote_address, |
860 | | const RefPtr<NrSocketBase> &external_socket) : |
861 | 0 | external_socket_(external_socket) { |
862 | 0 | // TODO(bug 1170299): Remove const_cast when no longer necessary |
863 | 0 | nr_transport_addr_copy(&remote_address_, |
864 | 0 | const_cast<nr_transport_addr*>(&remote_address)); |
865 | 0 | } |
866 | | |
867 | 0 | int TestNrSocket::PortMapping::send_from_queue() { |
868 | 0 | MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP); |
869 | 0 | int r = 0; |
870 | 0 |
|
871 | 0 | while (!send_queue_.empty()) { |
872 | 0 | UdpPacket &packet = *send_queue_.front(); |
873 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
874 | 0 | "PortMapping %s -> %s sending from queue to %s", |
875 | 0 | external_socket_->my_addr().as_string, |
876 | 0 | remote_address_.as_string, |
877 | 0 | packet.remote_address_.as_string); |
878 | 0 |
|
879 | 0 | r = external_socket_->sendto(packet.buffer_->data(), |
880 | 0 | packet.buffer_->len(), |
881 | 0 | 0, |
882 | 0 | &packet.remote_address_); |
883 | 0 |
|
884 | 0 | if (r) { |
885 | 0 | if (r != R_WOULDBLOCK) { |
886 | 0 | r_log(LOG_GENERIC, LOG_ERR, "%s: Fatal error %d, stop trying", |
887 | 0 | __FUNCTION__, r); |
888 | 0 | send_queue_.clear(); |
889 | 0 | } else { |
890 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "Would block, will retry later"); |
891 | 0 | } |
892 | 0 | break; |
893 | 0 | } |
894 | 0 |
|
895 | 0 | send_queue_.pop_front(); |
896 | 0 | } |
897 | 0 |
|
898 | 0 | return r; |
899 | 0 | } |
900 | | |
901 | | int TestNrSocket::PortMapping::sendto(const void *msg, |
902 | | size_t len, |
903 | 0 | const nr_transport_addr &to) { |
904 | 0 | MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP); |
905 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
906 | 0 | "PortMapping %s -> %s sending to %s", |
907 | 0 | external_socket_->my_addr().as_string, |
908 | 0 | remote_address_.as_string, |
909 | 0 | to.as_string); |
910 | 0 |
|
911 | 0 | last_used_ = PR_IntervalNow(); |
912 | 0 | int r = external_socket_->sendto(msg, len, 0, |
913 | 0 | // TODO(bug 1170299): Remove const_cast when no longer necessary |
914 | 0 | const_cast<nr_transport_addr*>(&to)); |
915 | 0 |
|
916 | 0 | if (r == R_WOULDBLOCK) { |
917 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, "Enqueueing UDP packet to %s", to.as_string); |
918 | 0 | send_queue_.push_back(RefPtr<UdpPacket>(new UdpPacket(msg, len, to))); |
919 | 0 | return 0; |
920 | 0 | } |
921 | 0 | if (r) { |
922 | 0 | r_log(LOG_GENERIC,LOG_ERR, "Error: %d", r); |
923 | 0 | } |
924 | 0 |
|
925 | 0 | return r; |
926 | 0 | } |
927 | | |
928 | | int TestNrSocket::PortMapping::async_wait(int how, NR_async_cb cb, void *cb_arg, |
929 | 0 | char *function, int line) { |
930 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
931 | 0 | "PortMapping %s -> %s waiting for %s", |
932 | 0 | external_socket_->my_addr().as_string, |
933 | 0 | remote_address_.as_string, |
934 | 0 | how == NR_ASYNC_WAIT_READ ? "read" : "write"); |
935 | 0 |
|
936 | 0 | return external_socket_->async_wait(how, cb, cb_arg, function, line); |
937 | 0 | } |
938 | | |
939 | 0 | int TestNrSocket::PortMapping::cancel(int how) { |
940 | 0 | r_log(LOG_GENERIC, LOG_DEBUG, |
941 | 0 | "PortMapping %s -> %s stop waiting for %s", |
942 | 0 | external_socket_->my_addr().as_string, |
943 | 0 | remote_address_.as_string, |
944 | 0 | how == NR_ASYNC_WAIT_READ ? "read" : "write"); |
945 | 0 |
|
946 | 0 | return external_socket_->cancel(how); |
947 | 0 | } |
948 | | |
949 | | } // namespace mozilla |
950 | | |