| /* | |
| * Copyright 2004 The WebRTC Project Authors. All rights reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "p2p/test/nat_server.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <memory> | |
| #include <span> | |
| #include <utility> | |
| #include "api/environment/environment.h" | |
| #include "p2p/test/nat_socket_factory.h" | |
| #include "p2p/test/nat_types.h" | |
| #include "rtc_base/async_packet_socket.h" | |
| #include "rtc_base/async_udp_socket.h" | |
| #include "rtc_base/buffer.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/ip_address.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/net_helpers.h" | |
| #include "rtc_base/network/received_packet.h" | |
| #include "rtc_base/proxy_server.h" | |
| #include "rtc_base/server_socket_adapters.h" | |
| #include "rtc_base/socket.h" | |
| #include "rtc_base/socket_adapters.h" | |
| #include "rtc_base/socket_address.h" | |
| #include "rtc_base/socket_address_pair.h" | |
| #include "rtc_base/socket_factory.h" | |
| #include "rtc_base/synchronization/mutex.h" | |
| #include "rtc_base/thread.h" | |
| namespace webrtc { | |
| RouteCmp::RouteCmp(NAT* nat) : symmetric(nat->IsSymmetric()) {} | |
| size_t RouteCmp::operator()(const SocketAddressPair& r) const { | |
| size_t h = r.source().Hash(); | |
| if (symmetric) | |
| h ^= r.destination().Hash(); | |
| return h; | |
| } | |
| bool RouteCmp::operator()(const SocketAddressPair& r1, | |
| const SocketAddressPair& r2) const { | |
| if (r1.source() < r2.source()) | |
| return true; | |
| if (r2.source() < r1.source()) | |
| return false; | |
| if (symmetric && (r1.destination() < r2.destination())) | |
| return true; | |
| if (symmetric && (r2.destination() < r1.destination())) | |
| return false; | |
| return false; | |
| } | |
| AddrCmp::AddrCmp(NAT* nat) | |
| : use_ip(nat->FiltersIP()), use_port(nat->FiltersPort()) {} | |
| size_t AddrCmp::operator()(const SocketAddress& a) const { | |
| size_t h = 0; | |
| if (use_ip) | |
| h ^= HashIP(a.ipaddr()); | |
| if (use_port) | |
| h ^= a.port() | (a.port() << 16); | |
| return h; | |
| } | |
| bool AddrCmp::operator()(const SocketAddress& a1, | |
| const SocketAddress& a2) const { | |
| if (use_ip && (a1.ipaddr() < a2.ipaddr())) | |
| return true; | |
| if (use_ip && (a2.ipaddr() < a1.ipaddr())) | |
| return false; | |
| if (use_port && (a1.port() < a2.port())) | |
| return true; | |
| if (use_port && (a2.port() < a1.port())) | |
| return false; | |
| return false; | |
| } | |
| // Proxy socket that will capture the external destination address intended for | |
| // a TCP connection to the NAT server. | |
| class NATProxyServerSocket : public AsyncProxyServerSocket { | |
| public: | |
| explicit NATProxyServerSocket(Socket* socket) | |
| : AsyncProxyServerSocket(socket, kNATEncodedIPv6AddressSize) { | |
| BufferInput(true); | |
| } | |
| void SendConnectResult(int err, const SocketAddress& addr) override { | |
| char code = err ? 1 : 0; | |
| BufferedReadAdapter::DirectSend(&code, sizeof(char)); | |
| } | |
| protected: | |
| void ProcessInput(char* data, size_t* len) override { | |
| if (*len < 2) { | |
| return; | |
| } | |
| int family = data[1]; | |
| RTC_DCHECK(family == AF_INET || family == AF_INET6); | |
| if ((family == AF_INET && *len < kNATEncodedIPv4AddressSize) || | |
| (family == AF_INET6 && *len < kNATEncodedIPv6AddressSize)) { | |
| return; | |
| } | |
| SocketAddress dest_addr; | |
| size_t address_length = UnpackAddressFromNAT( | |
| std::span(reinterpret_cast<const uint8_t*>(data), *len), &dest_addr); | |
| *len -= address_length; | |
| if (*len > 0) { | |
| memmove(data, data + address_length, *len); | |
| } | |
| bool remainder = (*len > 0); | |
| BufferInput(false); | |
| NotifyConnectRequest(this, dest_addr); | |
| if (remainder) { | |
| NotifyReadEvent(this); | |
| } | |
| } | |
| }; | |
| class NATProxyServer : public ProxyServer { | |
| public: | |
| NATProxyServer(SocketFactory* int_factory, | |
| const SocketAddress& int_addr, | |
| SocketFactory* ext_factory, | |
| const SocketAddress& ext_ip) | |
| : ProxyServer(int_factory, int_addr, ext_factory, ext_ip) {} | |
| protected: | |
| AsyncProxyServerSocket* WrapSocket(Socket* socket) override { | |
| return new NATProxyServerSocket(socket); | |
| } | |
| }; | |
| NATServer::NATServer(const Environment& env, | |
| NATType type, | |
| Thread& internal_socket_thread, | |
| SocketFactory* internal, | |
| const SocketAddress& internal_udp_addr, | |
| const SocketAddress& internal_tcp_addr, | |
| Thread& external_socket_thread, | |
| SocketFactory* external, | |
| const SocketAddress& external_ip) | |
| : env_(env), | |
| internal_socket_thread_(internal_socket_thread), | |
| external_socket_thread_(external_socket_thread), | |
| external_(external), | |
| external_ip_(external_ip.ipaddr(), 0) { | |
| nat_ = NAT::Create(type); | |
| internal_socket_thread_.BlockingCall([&] { | |
| udp_server_socket_ = | |
| AsyncUDPSocket::Create(env_, internal_udp_addr, *internal); | |
| udp_server_socket_->RegisterReceivedPacketCallback( | |
| [&](AsyncPacketSocket* socket, const ReceivedIpPacket& packet) { | |
| OnInternalUDPPacket(socket, packet); | |
| }); | |
| }); | |
| tcp_proxy_server_ = | |
| new NATProxyServer(internal, internal_tcp_addr, external, external_ip); | |
| int_map_ = new InternalMap(RouteCmp(nat_)); | |
| ext_map_ = new ExternalMap(); | |
| } | |
| NATServer::~NATServer() { | |
| for (InternalMap::iterator iter = int_map_->begin(); iter != int_map_->end(); | |
| iter++) | |
| delete iter->second; | |
| delete nat_; | |
| delete tcp_proxy_server_; | |
| delete int_map_; | |
| delete ext_map_; | |
| } | |
| void NATServer::OnInternalUDPPacket(AsyncPacketSocket* socket, | |
| const ReceivedIpPacket& packet) { | |
| RTC_DCHECK(internal_socket_thread_.IsCurrent()); | |
| // Read the intended destination from the wire. | |
| SocketAddress dest_addr; | |
| size_t length = UnpackAddressFromNAT(packet.payload(), &dest_addr); | |
| // Find the translation for these addresses (allocating one if necessary). | |
| SocketAddressPair route(packet.source_address(), dest_addr); | |
| InternalMap::iterator iter = int_map_->find(route); | |
| if (iter == int_map_->end()) { | |
| Translate(route); | |
| iter = int_map_->find(route); | |
| } | |
| RTC_DCHECK(iter != int_map_->end()); | |
| // Allow the destination to send packets back to the source. | |
| iter->second->AllowlistInsert(dest_addr); | |
| // Send the packet to its intended destination. | |
| AsyncSocketPacketOptions options; | |
| const char* buf = reinterpret_cast<const char*>(packet.payload().data()); | |
| size_t size = packet.payload().size(); | |
| iter->second->socket->SendTo(buf + length, size - length, dest_addr, options); | |
| } | |
| void NATServer::OnExternalUDPPacket(AsyncPacketSocket* socket, | |
| const ReceivedIpPacket& packet) { | |
| RTC_DCHECK(external_socket_thread_.IsCurrent()); | |
| SocketAddress local_addr = socket->GetLocalAddress(); | |
| // Find the translation for this addresses. | |
| ExternalMap::iterator iter = ext_map_->find(local_addr); | |
| RTC_DCHECK(iter != ext_map_->end()); | |
| // Allow the NAT to reject this packet. | |
| if (iter->second->ShouldFilterOut(packet.source_address())) { | |
| RTC_LOG(LS_INFO) << "Packet from " | |
| << packet.source_address().ToSensitiveString() | |
| << " was filtered out by the NAT."; | |
| return; | |
| } | |
| // Forward this packet to the internal address. | |
| // First prepend the address in a quasi-STUN format. | |
| Buffer real_buf = Buffer::CreateWithCapacity(packet.payload().size() + | |
| kNATEncodedIPv6AddressSize); | |
| PackAddressForNAT(packet.source_address(), real_buf); | |
| // Copy the data part after the address. | |
| AsyncSocketPacketOptions options; | |
| real_buf.AppendData(packet.payload()); | |
| udp_server_socket_->SendTo(real_buf.data(), real_buf.size(), | |
| iter->second->route.source(), options); | |
| } | |
| void NATServer::Translate(const SocketAddressPair& route) { | |
| external_socket_thread_.BlockingCall([&] { | |
| std::unique_ptr<AsyncUDPSocket> socket = | |
| AsyncUDPSocket::Create(env_, external_ip_, *external_); | |
| if (!socket) { | |
| RTC_LOG(LS_ERROR) << "Couldn't find a free port!"; | |
| return; | |
| } | |
| TransEntry* entry = new TransEntry(route, std::move(socket), nat_); | |
| (*int_map_)[route] = entry; | |
| (*ext_map_)[entry->socket->GetLocalAddress()] = entry; | |
| entry->socket->RegisterReceivedPacketCallback( | |
| [&](AsyncPacketSocket* socket, const ReceivedIpPacket& packet) { | |
| OnExternalUDPPacket(socket, packet); | |
| }); | |
| }); | |
| } | |
| NATServer::TransEntry::TransEntry(const SocketAddressPair& r, | |
| std::unique_ptr<AsyncUDPSocket> s, | |
| NAT* nat) | |
| : route(r), socket(std::move(s)), allowlist(AddrCmp(nat)) {} | |
| void NATServer::TransEntry::AllowlistInsert(const SocketAddress& addr) { | |
| MutexLock lock(&mutex_); | |
| allowlist.insert(addr); | |
| } | |
| bool NATServer::TransEntry::ShouldFilterOut(const SocketAddress& ext_addr) { | |
| MutexLock lock(&mutex_); | |
| return !allowlist.contains(ext_addr); | |
| } | |
| } // namespace webrtc |