michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Original author: ekr@rtfm.com michael@0: michael@0: /* michael@0: Original code from nICEr and nrappkit. michael@0: michael@0: nICEr copyright: michael@0: michael@0: Copyright (c) 2007, Adobe Systems, Incorporated michael@0: All rights reserved. michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions are michael@0: met: michael@0: michael@0: * Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: michael@0: * Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: michael@0: * Neither the name of Adobe Systems, Network Resonance nor the names of its michael@0: contributors may be used to endorse or promote products derived from michael@0: this software without specific prior written permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: nrappkit copyright: michael@0: michael@0: Copyright (C) 2001-2003, Network Resonance, Inc. michael@0: Copyright (C) 2006, Network Resonance, Inc. michael@0: All Rights Reserved michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions michael@0: are met: michael@0: michael@0: 1. Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: 2. Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: 3. Neither the name of Network Resonance, Inc. nor the name of any michael@0: contributors to this software may be used to endorse or promote michael@0: products derived from this software without specific prior written michael@0: permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' michael@0: AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE michael@0: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE michael@0: ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE michael@0: LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR michael@0: CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF michael@0: SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS michael@0: INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN michael@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) michael@0: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE michael@0: POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: ekr@rtfm.com Thu Dec 20 20:14:49 2001 michael@0: */ michael@0: #include "logging.h" michael@0: #include "mozilla/Scoped.h" michael@0: #include "databuffer.h" michael@0: michael@0: extern "C" { michael@0: #include "nr_api.h" michael@0: #include "async_wait.h" michael@0: #include "async_timer.h" michael@0: #include "nr_socket.h" michael@0: #include "nr_socket_local.h" michael@0: #include "transport_addr.h" michael@0: #include "addrs.h" michael@0: #include "local_addr.h" michael@0: #include "stun_util.h" michael@0: #include "registry.h" michael@0: } michael@0: michael@0: #include "stunserver.h" michael@0: michael@0: #include michael@0: michael@0: MOZ_MTLOG_MODULE("stunserver"); michael@0: michael@0: namespace mozilla { michael@0: michael@0: // Wrapper nr_socket which allows us to lie to the stun server about the michael@0: // IP address. michael@0: struct nr_socket_wrapped { michael@0: nr_socket *sock_; michael@0: nr_transport_addr addr_; michael@0: }; michael@0: michael@0: static int nr_socket_wrapped_destroy(void **objp) { michael@0: if (!objp || !*objp) michael@0: return 0; michael@0: michael@0: nr_socket_wrapped *wrapped = static_cast(*objp); michael@0: *objp = 0; michael@0: michael@0: delete wrapped; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int nr_socket_wrapped_sendto(void *obj, const void *msg, size_t len, int flags, michael@0: nr_transport_addr *addr) { michael@0: nr_socket_wrapped *wrapped = static_cast(obj); michael@0: michael@0: return nr_socket_sendto(wrapped->sock_, msg, len, flags, &wrapped->addr_); michael@0: } michael@0: michael@0: static int nr_socket_wrapped_recvfrom(void *obj, void * restrict buf, size_t maxlen, michael@0: size_t *len, int flags, nr_transport_addr *addr) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: static int nr_socket_wrapped_getfd(void *obj, NR_SOCKET *fd) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: static int nr_socket_wrapped_getaddr(void *obj, nr_transport_addr *addrp) { michael@0: nr_socket_wrapped *wrapped = static_cast(obj); michael@0: michael@0: return nr_socket_getaddr(wrapped->sock_, addrp); michael@0: } michael@0: michael@0: static int nr_socket_wrapped_close(void *obj) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: static int nr_socket_wrapped_set_send_addr(nr_socket *sock, nr_transport_addr *addr) { michael@0: nr_socket_wrapped *wrapped = static_cast(sock->obj); michael@0: michael@0: return nr_transport_addr_copy(&wrapped->addr_, addr); michael@0: } michael@0: michael@0: static nr_socket_vtbl nr_socket_wrapped_vtbl = { michael@0: 1, michael@0: nr_socket_wrapped_destroy, michael@0: nr_socket_wrapped_sendto, michael@0: nr_socket_wrapped_recvfrom, michael@0: nr_socket_wrapped_getfd, michael@0: nr_socket_wrapped_getaddr, michael@0: 0, michael@0: 0, michael@0: 0, michael@0: nr_socket_wrapped_close michael@0: }; michael@0: michael@0: int nr_socket_wrapped_create(nr_socket *inner, nr_socket **outp) { michael@0: ScopedDeletePtr wrapped(new nr_socket_wrapped()); michael@0: michael@0: wrapped->sock_ = inner; michael@0: michael@0: int r = nr_socket_create_int(wrapped.get(), &nr_socket_wrapped_vtbl, outp); michael@0: if (r) michael@0: return r; michael@0: michael@0: wrapped.forget(); michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: // Instance static. michael@0: // Note: Calling Create() at static init time is not going to be safe, since michael@0: // we have no reason to expect this will be initted to a nullptr yet. michael@0: TestStunServer* TestStunServer::instance; michael@0: uint16_t TestStunServer::instance_port = 3478; michael@0: michael@0: TestStunServer::~TestStunServer() { michael@0: // TODO(ekr@rtfm.com): Put this on the right thread. michael@0: michael@0: // Unhook callback from our listen socket. michael@0: if (listen_sock_) { michael@0: NR_SOCKET fd; michael@0: if (!nr_socket_getfd(listen_sock_, &fd)) { michael@0: NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ); michael@0: } michael@0: } michael@0: michael@0: // Free up stun context and network resources michael@0: nr_stun_server_ctx_destroy(&stun_server_); michael@0: nr_socket_destroy(&listen_sock_); michael@0: nr_socket_destroy(&send_sock_); michael@0: michael@0: // Make sure we aren't still waiting on a deferred response timer to pop michael@0: if (timer_handle_) michael@0: NR_async_timer_cancel(timer_handle_); michael@0: michael@0: delete response_addr_; michael@0: } michael@0: michael@0: int TestStunServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) { michael@0: michael@0: if (nr_transport_addr_set_port(&addr->addr, port)) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't set port"); michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: if (nr_transport_addr_fmt_addr_string(&addr->addr)) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't re-set addr string"); michael@0: return R_INTERNAL; michael@0: } michael@0: michael@0: if (nr_socket_local_create(&addr->addr, &listen_sock_)) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't create listen socket"); michael@0: return R_ALREADY; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: TestStunServer* TestStunServer::Create() { michael@0: NR_reg_init(NR_REG_MODE_LOCAL); michael@0: michael@0: ScopedDeletePtr server(new TestStunServer()); michael@0: michael@0: nr_local_addr addrs[100]; michael@0: int addr_ct; michael@0: int r; michael@0: michael@0: r = nr_stun_find_local_addresses(addrs, 100, &addr_ct); michael@0: if (r) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't retrieve addresses"); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (addr_ct < 1) { michael@0: MOZ_MTLOG(ML_ERROR, "No local addresses"); michael@0: return nullptr; michael@0: } michael@0: michael@0: NR_SOCKET fd; michael@0: int tries = 10; michael@0: while (tries--) { michael@0: // Bind to the first address (arbitrarily) on configured port (default 3478) michael@0: r = server->TryOpenListenSocket(&addrs[0], instance_port); michael@0: // We interpret R_ALREADY to mean the addr is probably in use. Try another. michael@0: // Otherwise, it either worked or it didn't, and we check below. michael@0: if (r != R_ALREADY) { michael@0: break; michael@0: } michael@0: ++instance_port; michael@0: } michael@0: michael@0: if (r) { michael@0: return nullptr; michael@0: } michael@0: michael@0: r = nr_socket_getfd(server->listen_sock_, &fd); michael@0: if (r) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't get fd"); michael@0: return nullptr; michael@0: } michael@0: michael@0: r = nr_socket_wrapped_create(server->listen_sock_, &server->send_sock_); michael@0: if (r) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't create send socket"); michael@0: return nullptr; michael@0: } michael@0: michael@0: r = nr_stun_server_ctx_create(const_cast("Test STUN server"), michael@0: server->send_sock_, michael@0: &server->stun_server_); michael@0: if (r) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't create STUN server"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Cache the address and port. michael@0: char addr_string[INET6_ADDRSTRLEN]; michael@0: r = nr_transport_addr_get_addrstring(&addrs[0].addr, addr_string, michael@0: sizeof(addr_string)); michael@0: if (r) { michael@0: MOZ_MTLOG(ML_ERROR, "Failed to convert listen addr to a string representation"); michael@0: return nullptr; michael@0: } michael@0: michael@0: server->listen_addr_ = addr_string; michael@0: server->listen_port_ = instance_port; michael@0: michael@0: NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server.get()); michael@0: michael@0: return server.forget(); michael@0: } michael@0: michael@0: void TestStunServer::ConfigurePort(uint16_t port) { michael@0: instance_port = port; michael@0: } michael@0: michael@0: TestStunServer* TestStunServer::GetInstance() { michael@0: if (!instance) michael@0: instance = Create(); michael@0: michael@0: MOZ_ASSERT(instance); michael@0: return instance; michael@0: } michael@0: michael@0: void TestStunServer::ShutdownInstance() { michael@0: delete instance; michael@0: michael@0: instance = nullptr; michael@0: } michael@0: michael@0: michael@0: struct DeferredStunOperation { michael@0: DeferredStunOperation(TestStunServer *server, michael@0: const char *data, size_t len, michael@0: nr_transport_addr *addr) : michael@0: server_(server), michael@0: buffer_(reinterpret_cast(data), len) { michael@0: nr_transport_addr_copy(&addr_, addr); michael@0: } michael@0: michael@0: TestStunServer *server_; michael@0: DataBuffer buffer_; michael@0: nr_transport_addr addr_; michael@0: }; michael@0: michael@0: void TestStunServer::Process(const uint8_t *msg, size_t len, nr_transport_addr *addr) { michael@0: // Set the wrapped address so that the response goes to the right place. michael@0: nr_socket_wrapped_set_send_addr(send_sock_, addr); michael@0: nr_stun_server_process_request(stun_server_, send_sock_, michael@0: const_cast(reinterpret_cast(msg)), michael@0: len, michael@0: response_addr_ ? michael@0: response_addr_ : addr, michael@0: NR_STUN_AUTH_RULE_OPTIONAL); michael@0: } michael@0: michael@0: void TestStunServer::process_cb(NR_SOCKET s, int how, void *cb_arg) { michael@0: DeferredStunOperation *op = static_cast(cb_arg); michael@0: op->server_->timer_handle_ = nullptr; michael@0: op->server_->Process(op->buffer_.data(), op->buffer_.len(), &op->addr_); michael@0: michael@0: delete op; michael@0: } michael@0: michael@0: void TestStunServer::readable_cb(NR_SOCKET s, int how, void *cb_arg) { michael@0: TestStunServer* server = static_cast(cb_arg); michael@0: michael@0: char message[4096]; michael@0: size_t message_len; michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_socket_recvfrom(server->listen_sock_, message, sizeof(message), michael@0: &message_len, 0, &addr); michael@0: michael@0: if (r) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't read STUN message"); michael@0: return; michael@0: } michael@0: michael@0: MOZ_MTLOG(ML_DEBUG, "Received data of length " << message_len); michael@0: michael@0: // Re-arm. michael@0: NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server); michael@0: michael@0: michael@0: // If we have initial dropping set, check at this point. michael@0: std::string key(addr.as_string); michael@0: michael@0: if (server->received_ct_.count(key) == 0) { michael@0: server->received_ct_[key] = 0; michael@0: } michael@0: michael@0: ++server->received_ct_[key]; michael@0: michael@0: if (!server->active_ || (server->received_ct_[key] <= server->initial_ct_)) { michael@0: MOZ_MTLOG(ML_DEBUG, "Dropping message #" michael@0: << server->received_ct_[key] << " from " << key); michael@0: return; michael@0: } michael@0: michael@0: if (server->delay_ms_) { michael@0: NR_ASYNC_TIMER_SET(server->delay_ms_, michael@0: process_cb, michael@0: new DeferredStunOperation( michael@0: server, michael@0: message, message_len, michael@0: &addr), michael@0: &server->timer_handle_); michael@0: } else { michael@0: server->Process(reinterpret_cast(message), message_len, &addr); michael@0: } michael@0: } michael@0: michael@0: void TestStunServer::SetActive(bool active) { michael@0: active_ = active; michael@0: } michael@0: michael@0: void TestStunServer::SetDelay(uint32_t delay_ms) { michael@0: delay_ms_ = delay_ms; michael@0: } michael@0: michael@0: void TestStunServer::SetDropInitialPackets(uint32_t count) { michael@0: initial_ct_ = count; michael@0: } michael@0: michael@0: nsresult TestStunServer::SetResponseAddr(nr_transport_addr *addr) { michael@0: delete response_addr_; michael@0: michael@0: response_addr_ = new nr_transport_addr(); michael@0: michael@0: int r = nr_transport_addr_copy(response_addr_, addr); michael@0: if (r) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TestStunServer::SetResponseAddr(const std::string& addr, michael@0: uint16_t port) { michael@0: nr_transport_addr addr2; michael@0: michael@0: int r = nr_ip4_str_port_to_transport_addr(addr.c_str(), michael@0: port, IPPROTO_UDP, michael@0: &addr2); michael@0: if (r) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return SetResponseAddr(&addr2); michael@0: } michael@0: michael@0: void TestStunServer::Reset() { michael@0: delay_ms_ = 0; michael@0: if (timer_handle_) { michael@0: NR_async_timer_cancel(timer_handle_); michael@0: timer_handle_ = nullptr; michael@0: } michael@0: delete response_addr_; michael@0: response_addr_ = nullptr; michael@0: } michael@0: michael@0: } // close namespace