From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- media/mtransport/test_nr_socket.cpp | 892 ++++++++++++++++++++++++++++++++++++ 1 file changed, 892 insertions(+) create mode 100644 media/mtransport/test_nr_socket.cpp (limited to 'media/mtransport/test_nr_socket.cpp') diff --git a/media/mtransport/test_nr_socket.cpp b/media/mtransport/test_nr_socket.cpp new file mode 100644 index 000000000..5f7d69a78 --- /dev/null +++ b/media/mtransport/test_nr_socket.cpp @@ -0,0 +1,892 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* +*/ + +/* +Based partially on original code from nICEr and nrappkit. + +nICEr copyright: + +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +nrappkit copyright: + + Copyright (C) 2001-2003, Network Resonance, Inc. + Copyright (C) 2006, Network Resonance, Inc. + All Rights Reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Network Resonance, Inc. nor the name of any + contributors to this software may be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + ekr@rtfm.com Thu Dec 20 20:14:49 2001 +*/ + +// Original author: bcampen@mozilla.com [:bwc] + +extern "C" { +#include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE +#include "nr_api.h" +#include "async_wait.h" +#include "async_timer.h" +#include "nr_socket.h" +#include "nr_socket_local.h" +#include "stun_hint.h" +#include "transport_addr.h" +} + +#include "mozilla/RefPtr.h" +#include "test_nr_socket.h" +#include "runnable_utils.h" + +namespace mozilla { + +static int test_nat_socket_create(void *obj, + nr_transport_addr *addr, + nr_socket **sockp) { + RefPtr sock = new TestNrSocket(static_cast(obj)); + + int r, _status; + + r = sock->create(addr); + if (r) + ABORT(r); + + r = nr_socket_create_int(static_cast(sock), + sock->vtbl(), sockp); + if (r) + ABORT(r); + + _status = 0; + + { + // We will release this reference in destroy(), not exactly the normal + // ownership model, but it is what it is. + NrSocketBase *dummy = sock.forget().take(); + (void)dummy; + } + +abort: + return _status; +} + +static int test_nat_socket_factory_destroy(void **obj) { + TestNat *nat = static_cast(*obj); + *obj = nullptr; + nat->Release(); + return 0; +} + +static nr_socket_factory_vtbl test_nat_socket_factory_vtbl = { + test_nat_socket_create, + test_nat_socket_factory_destroy +}; + +/* static */ +TestNat::NatBehavior +TestNat::ToNatBehavior(const std::string& type) { + if (!type.compare("ENDPOINT_INDEPENDENT")) { + return TestNat::ENDPOINT_INDEPENDENT; + } else if (!type.compare("ADDRESS_DEPENDENT")) { + return TestNat::ADDRESS_DEPENDENT; + } else if (!type.compare("PORT_DEPENDENT")) { + return TestNat::PORT_DEPENDENT; + } + + MOZ_ASSERT(false, "Invalid NAT behavior"); + return TestNat::ENDPOINT_INDEPENDENT; +} + +bool TestNat::has_port_mappings() const { + for (TestNrSocket *sock : sockets_) { + if (sock->has_port_mappings()) { + return true; + } + } + return false; +} + +bool TestNat::is_my_external_tuple(const nr_transport_addr &addr) const { + for (TestNrSocket *sock : sockets_) { + if (sock->is_my_external_tuple(addr)) { + return true; + } + } + + return false; +} + +bool TestNat::is_an_internal_tuple(const nr_transport_addr &addr) const { + for (TestNrSocket *sock : sockets_) { + nr_transport_addr addr_behind_nat; + if (sock->getaddr(&addr_behind_nat)) { + MOZ_CRASH("TestNrSocket::getaddr failed!"); + } + + // TODO(bug 1170299): Remove const_cast when no longer necessary + if (!nr_transport_addr_cmp(const_cast(&addr), + &addr_behind_nat, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) { + return true; + } + } + return false; +} + +int TestNat::create_socket_factory(nr_socket_factory **factorypp) { + int r = nr_socket_factory_create_int(this, + &test_nat_socket_factory_vtbl, + factorypp); + if (!r) { + AddRef(); + } + return r; +} + +TestNrSocket::TestNrSocket(TestNat *nat) + : nat_(nat), + timer_handle_(nullptr) { + nat_->insert_socket(this); +} + +TestNrSocket::~TestNrSocket() { + nat_->erase_socket(this); +} + +RefPtr TestNrSocket::create_external_socket( + const nr_transport_addr &dest_addr) const { + MOZ_ASSERT(nat_->enabled_); + MOZ_ASSERT(!nat_->is_an_internal_tuple(dest_addr)); + + int r; + nr_transport_addr nat_external_addr; + + // Open the socket on an arbitrary port, on the same address. + // TODO(bug 1170299): Remove const_cast when no longer necessary + if ((r = nr_transport_addr_copy( + &nat_external_addr, + const_cast(&internal_socket_->my_addr())))) { + r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_copy: %d", + __FUNCTION__, r); + return nullptr; + } + + if ((r = nr_transport_addr_set_port(&nat_external_addr, 0))) { + r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in nr_transport_addr_set_port: %d", + __FUNCTION__, r); + return nullptr; + } + + RefPtr external_socket; + r = NrSocketBase::CreateSocket(&nat_external_addr, &external_socket); + + if (r) { + r_log(LOG_GENERIC,LOG_CRIT, "%s: Failure in NrSocket::create: %d", + __FUNCTION__, r); + return nullptr; + } + + return external_socket; +} + +int TestNrSocket::create(nr_transport_addr *addr) { + return NrSocketBase::CreateSocket(addr, &internal_socket_); +} + +int TestNrSocket::getaddr(nr_transport_addr *addrp) { + return internal_socket_->getaddr(addrp); +} + +void TestNrSocket::close() { + if (timer_handle_) { + NR_async_timer_cancel(timer_handle_); + timer_handle_ = 0; + } + internal_socket_->close(); + for (RefPtr& port_mapping : port_mappings_) { + port_mapping->external_socket_->close(); + } +} + +int TestNrSocket::listen(int backlog) { + MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP); + r_log(LOG_GENERIC, LOG_DEBUG, + "TestNrSocket %s listening", + internal_socket_->my_addr().as_string); + + return internal_socket_->listen(backlog); +} + +int TestNrSocket::accept(nr_transport_addr *addrp, nr_socket **sockp) { + MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP); + int r = internal_socket_->accept(addrp, sockp); + if (r) { + return r; + } + + if (nat_->enabled_ && !nat_->is_an_internal_tuple(*addrp)) { + nr_socket_destroy(sockp); + return R_IO_ERROR; + } + + return 0; +} + +void TestNrSocket::process_delayed_cb(NR_SOCKET s, int how, void *cb_arg) { + DeferredPacket *op = static_cast(cb_arg); + op->socket_->timer_handle_ = nullptr; + r_log(LOG_GENERIC, LOG_DEBUG, + "TestNrSocket %s sending delayed STUN response", + op->internal_socket_->my_addr().as_string); + op->internal_socket_->sendto(op->buffer_.data(), op->buffer_.len(), + op->flags_, &op->to_); + + delete op; +} + +int TestNrSocket::sendto(const void *msg, size_t len, + int flags, nr_transport_addr *to) { + MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP); + + UCHAR *buf = static_cast(const_cast(msg)); + if (nat_->block_stun_ && + nr_is_stun_message(buf, len)) { + return 0; + } + + /* TODO: improve the functionality of this in bug 1253657 */ + if (!nat_->enabled_ || nat_->is_an_internal_tuple(*to)) { + if (nat_->delay_stun_resp_ms_ && + nr_is_stun_response_message(buf, len)) { + NR_ASYNC_TIMER_SET(nat_->delay_stun_resp_ms_, + process_delayed_cb, + new DeferredPacket(this, msg, len, flags, to, + internal_socket_), + &timer_handle_); + return 0; + } + return internal_socket_->sendto(msg, len, flags, to); + } + + destroy_stale_port_mappings(); + + if (to->protocol == IPPROTO_UDP && nat_->block_udp_) { + // Silently eat the packet + return 0; + } + + // Choose our port mapping based on our most selective criteria + PortMapping *port_mapping = get_port_mapping(*to, + std::max(nat_->filtering_type_, + nat_->mapping_type_)); + + if (!port_mapping) { + // See if we have already made the external socket we need to use. + PortMapping *similar_port_mapping = + get_port_mapping(*to, nat_->mapping_type_); + RefPtr external_socket; + + if (similar_port_mapping) { + external_socket = similar_port_mapping->external_socket_; + } else { + external_socket = create_external_socket(*to); + if (!external_socket) { + MOZ_ASSERT(false); + return R_INTERNAL; + } + } + + port_mapping = create_port_mapping(*to, external_socket); + port_mappings_.push_back(port_mapping); + + if (poll_flags() & PR_POLL_READ) { + // Make sure the new port mapping is ready to receive traffic if the + // TestNrSocket is already waiting. + port_mapping->async_wait(NR_ASYNC_WAIT_READ, + socket_readable_callback, + this, + (char*)__FUNCTION__, + __LINE__); + } + } + + // We probably don't want to propagate the flags, since this is a simulated + // external IP address. + return port_mapping->sendto(msg, len, *to); +} + +int TestNrSocket::recvfrom(void *buf, size_t maxlen, + size_t *len, int flags, + nr_transport_addr *from) { + MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP); + + int r; + bool ingress_allowed = false; + + if (readable_socket_) { + // If any of the external sockets got data, see if it will be passed through + r = readable_socket_->recvfrom(buf, maxlen, len, 0, from); + readable_socket_ = nullptr; + if (!r) { + PortMapping *port_mapping_used; + ingress_allowed = allow_ingress(*from, &port_mapping_used); + if (ingress_allowed) { + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s via %s", + internal_socket_->my_addr().as_string, + from->as_string, + port_mapping_used->external_socket_->my_addr().as_string); + if (nat_->refresh_on_ingress_) { + port_mapping_used->last_used_ = PR_IntervalNow(); + } + } + } + } else { + // If no external socket has data, see if there's any data that was sent + // directly to the TestNrSocket, and eat it if it isn't supposed to get + // through. + r = internal_socket_->recvfrom(buf, maxlen, len, flags, from); + if (!r) { + // We do not use allow_ingress() here because that only handles traffic + // landing on an external port. + ingress_allowed = (!nat_->enabled_ || + nat_->is_an_internal_tuple(*from)); + if (!ingress_allowed) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Not behind the same NAT", + internal_socket_->my_addr().as_string, + from->as_string); + } else { + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s", + internal_socket_->my_addr().as_string, + from->as_string); + } + } + } + + // Kinda lame that we are forced to give the app a readable callback and then + // say "Oh, never mind...", but the alternative is to totally decouple the + // callbacks from STS and the callbacks the app sets. On the bright side, this + // speeds up unit tests where we are verifying that ingress is forbidden, + // since they'll get a readable callback and then an error, instead of having + // to wait for a timeout. + if (!ingress_allowed) { + *len = 0; + r = R_WOULDBLOCK; + } + + return r; +} + +bool TestNrSocket::allow_ingress(const nr_transport_addr &from, + PortMapping **port_mapping_used) const { + // This is only called for traffic arriving at a port mapping + MOZ_ASSERT(nat_->enabled_); + MOZ_ASSERT(!nat_->is_an_internal_tuple(from)); + + *port_mapping_used = get_port_mapping(from, nat_->filtering_type_); + if (!(*port_mapping_used)) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Filtered", + internal_socket_->my_addr().as_string, + from.as_string); + return false; + } + + if (is_port_mapping_stale(**port_mapping_used)) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Stale port mapping", + internal_socket_->my_addr().as_string, + from.as_string); + return false; + } + + if (!nat_->allow_hairpinning_ && nat_->is_my_external_tuple(from)) { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s denying ingress from %s: " + "Hairpinning disallowed", + internal_socket_->my_addr().as_string, + from.as_string); + return false; + } + + return true; +} + +int TestNrSocket::connect(nr_transport_addr *addr) { + + if (connect_invoked_ || !port_mappings_.empty()) { + MOZ_CRASH("TestNrSocket::connect() called more than once!"); + return R_INTERNAL; + } + + if (!nat_->enabled_ + || addr->protocol==IPPROTO_UDP // Horrible hack to allow default address + // discovery to work. Only works because + // we don't normally connect on UDP. + || nat_->is_an_internal_tuple(*addr)) { + // This will set connect_invoked_ + return internal_socket_->connect(addr); + } + + RefPtr external_socket(create_external_socket(*addr)); + if (!external_socket) { + return R_INTERNAL; + } + + PortMapping *port_mapping = create_port_mapping(*addr, external_socket); + port_mappings_.push_back(port_mapping); + int r = port_mapping->external_socket_->connect(addr); + if (r && r != R_WOULDBLOCK) { + return r; + } + + port_mapping->last_used_ = PR_IntervalNow(); + + if (poll_flags() & PR_POLL_READ) { + port_mapping->async_wait(NR_ASYNC_WAIT_READ, + port_mapping_tcp_passthrough_callback, + this, + (char*)__FUNCTION__, + __LINE__); + } + + return r; +} + +int TestNrSocket::write(const void *msg, size_t len, size_t *written) { + + if (port_mappings_.empty()) { + // The no-nat case, just pass call through. + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s writing", + my_addr().as_string); + + return internal_socket_->write(msg, len, written); + } else { + destroy_stale_port_mappings(); + if (port_mappings_.empty()) { + return -1; + } + // This is TCP only + MOZ_ASSERT(port_mappings_.size() == 1); + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s writing", + port_mappings_.front()->external_socket_->my_addr().as_string, + port_mappings_.front()->remote_address_.as_string); + + port_mappings_.front()->last_used_ = PR_IntervalNow(); + return port_mappings_.front()->external_socket_->write(msg, len, written); + } +} + +int TestNrSocket::read(void *buf, size_t maxlen, size_t *len) { + + if (port_mappings_.empty()) { + return internal_socket_->read(buf, maxlen, len); + } else { + MOZ_ASSERT(port_mappings_.size() == 1); + int bytesRead = + port_mappings_.front()->external_socket_->read(buf, maxlen, len); + if (bytesRead > 0 && nat_->refresh_on_ingress_) { + port_mappings_.front()->last_used_ = PR_IntervalNow(); + } + return bytesRead; + } +} + +int TestNrSocket::async_wait(int how, NR_async_cb cb, void *cb_arg, + char *function, int line) { + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s waiting for %s", + internal_socket_->my_addr().as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + int r; + + if (how == NR_ASYNC_WAIT_READ) { + NrSocketBase::async_wait(how, cb, cb_arg, function, line); + + // Make sure we're waiting on the socket for the internal address + r = internal_socket_->async_wait(how, + socket_readable_callback, + this, + function, + line); + } else { + // For write, just use the readiness of the internal socket, since we queue + // everything for the port mappings. + r = internal_socket_->async_wait(how, + cb, + cb_arg, + function, + line); + } + + if (r) { + r_log(LOG_GENERIC, LOG_ERR, "TestNrSocket %s failed to async_wait for " + "internal socket: %d\n", + internal_socket_->my_addr().as_string, + r); + return r; + } + + if (is_tcp_connection_behind_nat()) { + // Bypass all port-mapping related logic + return 0; + } + + if (internal_socket_->my_addr().protocol == IPPROTO_TCP) { + // For a TCP connection through a simulated NAT, these signals are + // just passed through. + MOZ_ASSERT(port_mappings_.size() == 1); + + return port_mappings_.front()->async_wait( + how, + port_mapping_tcp_passthrough_callback, + this, + function, + line); + } else if (how == NR_ASYNC_WAIT_READ) { + // For UDP port mappings, we decouple the writeable callbacks + for (PortMapping *port_mapping : port_mappings_) { + // Be ready to receive traffic on our port mappings + r = port_mapping->async_wait(how, + socket_readable_callback, + this, + function, + line); + if (r) { + r_log(LOG_GENERIC, LOG_ERR, "TestNrSocket %s failed to async_wait for " + "port mapping: %d\n", + internal_socket_->my_addr().as_string, + r); + return r; + } + } + } + + return 0; +} + +void TestNrSocket::cancel_port_mapping_async_wait(int how) { + for (PortMapping *port_mapping : port_mappings_) { + port_mapping->cancel(how); + } +} + +int TestNrSocket::cancel(int how) { + + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s stop waiting for %s", + internal_socket_->my_addr().as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + // Writable callbacks are decoupled except for the TCP case + if (how == NR_ASYNC_WAIT_READ || + internal_socket_->my_addr().protocol == IPPROTO_TCP) { + cancel_port_mapping_async_wait(how); + } + + return internal_socket_->cancel(how); +} + +bool TestNrSocket::has_port_mappings() const { + return !port_mappings_.empty(); +} + +bool TestNrSocket::is_my_external_tuple(const nr_transport_addr &addr) const { + for (PortMapping *port_mapping : port_mappings_) { + nr_transport_addr port_mapping_addr; + if (port_mapping->external_socket_->getaddr(&port_mapping_addr)) { + MOZ_CRASH("NrSocket::getaddr failed!"); + } + + // TODO(bug 1170299): Remove const_cast when no longer necessary + if (!nr_transport_addr_cmp(const_cast(&addr), + &port_mapping_addr, + NR_TRANSPORT_ADDR_CMP_MODE_ALL)) { + return true; + } + } + return false; +} + +bool TestNrSocket::is_port_mapping_stale( + const PortMapping &port_mapping) const { + PRIntervalTime now = PR_IntervalNow(); + PRIntervalTime elapsed_ticks = now - port_mapping.last_used_; + uint32_t idle_duration = PR_IntervalToMilliseconds(elapsed_ticks); + return idle_duration > nat_->mapping_timeout_; +} + +void TestNrSocket::destroy_stale_port_mappings() { + for (auto i = port_mappings_.begin(); i != port_mappings_.end();) { + auto temp = i; + ++i; + if (is_port_mapping_stale(**temp)) { + r_log(LOG_GENERIC, LOG_INFO, + "TestNrSocket %s destroying port mapping %s -> %s", + internal_socket_->my_addr().as_string, + (*temp)->external_socket_->my_addr().as_string, + (*temp)->remote_address_.as_string); + + port_mappings_.erase(temp); + } + } +} + +void TestNrSocket::socket_readable_callback(void *real_sock_v, + int how, + void *test_sock_v) { + TestNrSocket *test_socket = static_cast(test_sock_v); + NrSocketBase *real_socket = static_cast(real_sock_v); + + test_socket->on_socket_readable(real_socket); +} + +void TestNrSocket::on_socket_readable(NrSocketBase *real_socket) { + if (!readable_socket_ && (real_socket != internal_socket_)) { + readable_socket_ = real_socket; + } + + fire_readable_callback(); +} + +void TestNrSocket::fire_readable_callback() { + MOZ_ASSERT(poll_flags() & PR_POLL_READ); + r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s ready for read", + internal_socket_->my_addr().as_string); + fire_callback(NR_ASYNC_WAIT_READ); +} + +void TestNrSocket::port_mapping_writeable_callback(void *ext_sock_v, + int how, + void *test_sock_v) { + TestNrSocket *test_socket = static_cast(test_sock_v); + NrSocketBase *external_socket = static_cast(ext_sock_v); + + test_socket->write_to_port_mapping(external_socket); +} + +void TestNrSocket::write_to_port_mapping(NrSocketBase *external_socket) { + MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP); + + int r = 0; + for (PortMapping *port_mapping : port_mappings_) { + if (port_mapping->external_socket_ == external_socket) { + // If the send succeeds, or if there was nothing to send, we keep going + r = port_mapping->send_from_queue(); + if (r) { + break; + } + } + } + + if (r == R_WOULDBLOCK) { + // Re-register for writeable callbacks, since we still have stuff to send + NR_ASYNC_WAIT(external_socket, + NR_ASYNC_WAIT_WRITE, + &TestNrSocket::port_mapping_writeable_callback, + this); + } +} + +void TestNrSocket::port_mapping_tcp_passthrough_callback(void *ext_sock_v, + int how, + void *test_sock_v) { + TestNrSocket *test_socket = static_cast(test_sock_v); + r_log(LOG_GENERIC, LOG_DEBUG, + "TestNrSocket %s firing %s callback", + test_socket->internal_socket_->my_addr().as_string, + how == NR_ASYNC_WAIT_READ ? "readable" : "writeable"); + + + test_socket->internal_socket_->fire_callback(how); +} + +bool TestNrSocket::is_tcp_connection_behind_nat() const { + return internal_socket_->my_addr().protocol == IPPROTO_TCP && + port_mappings_.empty(); +} + +TestNrSocket::PortMapping* TestNrSocket::get_port_mapping( + const nr_transport_addr &remote_address, + TestNat::NatBehavior filter) const { + int compare_flags; + switch (filter) { + case TestNat::ENDPOINT_INDEPENDENT: + compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL; + break; + case TestNat::ADDRESS_DEPENDENT: + compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ADDR; + break; + case TestNat::PORT_DEPENDENT: + compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ALL; + break; + } + + for (PortMapping *port_mapping : port_mappings_) { + // TODO(bug 1170299): Remove const_cast when no longer necessary + if (!nr_transport_addr_cmp(const_cast(&remote_address), + &port_mapping->remote_address_, + compare_flags)) + return port_mapping; + } + return nullptr; +} + +TestNrSocket::PortMapping* TestNrSocket::create_port_mapping( + const nr_transport_addr &remote_address, + const RefPtr &external_socket) const { + r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s creating port mapping %s -> %s", + internal_socket_->my_addr().as_string, + external_socket->my_addr().as_string, + remote_address.as_string); + + return new PortMapping(remote_address, external_socket); +} + +TestNrSocket::PortMapping::PortMapping( + const nr_transport_addr &remote_address, + const RefPtr &external_socket) : + external_socket_(external_socket) { + // TODO(bug 1170299): Remove const_cast when no longer necessary + nr_transport_addr_copy(&remote_address_, + const_cast(&remote_address)); +} + +int TestNrSocket::PortMapping::send_from_queue() { + MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP); + int r = 0; + + while (!send_queue_.empty()) { + UdpPacket &packet = *send_queue_.front(); + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s sending from queue to %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + packet.remote_address_.as_string); + + r = external_socket_->sendto(packet.buffer_->data(), + packet.buffer_->len(), + 0, + &packet.remote_address_); + + if (r) { + if (r != R_WOULDBLOCK) { + r_log(LOG_GENERIC, LOG_ERR, "%s: Fatal error %d, stop trying", + __FUNCTION__, r); + send_queue_.clear(); + } else { + r_log(LOG_GENERIC, LOG_DEBUG, "Would block, will retry later"); + } + break; + } + + send_queue_.pop_front(); + } + + return r; +} + +int TestNrSocket::PortMapping::sendto(const void *msg, + size_t len, + const nr_transport_addr &to) { + MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP); + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s sending to %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + to.as_string); + + last_used_ = PR_IntervalNow(); + int r = external_socket_->sendto(msg, len, 0, + // TODO(bug 1170299): Remove const_cast when no longer necessary + const_cast(&to)); + + if (r == R_WOULDBLOCK) { + r_log(LOG_GENERIC, LOG_DEBUG, "Enqueueing UDP packet to %s", to.as_string); + send_queue_.push_back(RefPtr(new UdpPacket(msg, len, to))); + return 0; + } else if (r) { + r_log(LOG_GENERIC,LOG_ERR, "Error: %d", r); + } + + return r; +} + +int TestNrSocket::PortMapping::async_wait(int how, NR_async_cb cb, void *cb_arg, + char *function, int line) { + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s waiting for %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + return external_socket_->async_wait(how, cb, cb_arg, function, line); +} + +int TestNrSocket::PortMapping::cancel(int how) { + r_log(LOG_GENERIC, LOG_DEBUG, + "PortMapping %s -> %s stop waiting for %s", + external_socket_->my_addr().as_string, + remote_address_.as_string, + how == NR_ASYNC_WAIT_READ ? "read" : "write"); + + return external_socket_->cancel(how); +} + +} // namespace mozilla + -- cgit v1.2.3