#include "network_universe.hh" #include #include #include "fake_sockets.hh" namespace tox::test { std::ostream &operator<<(std::ostream &os, TcpFlags flags) { bool first = true; if (flags.value & 0x02) { os << (first ? "" : "|") << "SYN"; first = false; } if (flags.value & 0x10) { os << (first ? "" : "|") << "ACK"; first = false; } if (flags.value & 0x01) { os << (first ? "" : "|") << "FIN"; first = false; } if (flags.value & 0x04) { os << (first ? "" : "|") << "RST"; first = false; } if (first) os << "NONE"; return os << "(" << static_cast(flags.value) << ")"; } bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const { if (port != other.port) return port < other.port; if (ip.family.value != other.ip.family.value) return ip.family.value < other.ip.family.value; if (net_family_is_ipv4(ip.family)) { return ip.ip.v4.uint32 < other.ip.ip.v4.uint32; } return std::memcmp(&ip.ip.v6, &other.ip.ip.v6, sizeof(ip.ip.v6)) < 0; } NetworkUniverse::NetworkUniverse() { } NetworkUniverse::~NetworkUniverse() { } bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull socket) { std::lock_guard lock(mutex_); IP_Port_Key key{ip, port}; if (udp_bindings_.count(key)) return false; udp_bindings_[key] = socket; return true; } void NetworkUniverse::unbind_udp(IP ip, uint16_t port) { std::lock_guard lock(mutex_); udp_bindings_.erase({ip, port}); } bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket) { std::lock_guard lock(mutex_); tcp_bindings_.insert({{ip, port}, socket}); return true; } void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket) { std::lock_guard lock(mutex_); auto range = tcp_bindings_.equal_range({ip, port}); for (auto it = range.first; it != range.second; ++it) { if (it->second == socket) { tcp_bindings_.erase(it); break; } } } void NetworkUniverse::send_packet(Packet p) { // Apply filters for (const auto &filter : filters_) { if (!filter(p)) return; } // Notify observers for (const auto &observer : observers_) { observer(p); } p.delivery_time += global_latency_ms_; std::lock_guard lock(mutex_); p.sequence_number = next_packet_id_++; if (verbose_) { Ip_Ntoa from_str, to_str; net_ip_ntoa(&p.from.ip, &from_str); net_ip_ntoa(&p.to.ip, &to_str); std::cerr << "[NetworkUniverse] Enqueued packet #" << p.sequence_number << " from " << from_str.buf << ":" << net_ntohs(p.from.port) << " to " << to_str.buf << ":" << net_ntohs(p.to.port); if (p.is_tcp) { std::cerr << " (TCP Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq << " Ack=" << p.ack << ")"; } std::cerr << " with size " << p.data.size() << std::endl; } event_queue_.push(std::move(p)); } static bool is_ipv4_mapped(const IP &ip) { if (!net_family_is_ipv6(ip.family)) return false; const uint8_t *b = ip.ip.v6.uint8; for (int i = 0; i < 10; ++i) if (b[i] != 0) return false; if (b[10] != 0xFF || b[11] != 0xFF) return false; return true; } static IP extract_ipv4(const IP &ip) { IP ip4; ip_init(&ip4, false); const uint8_t *b = ip.ip.v6.uint8; std::memcpy(ip4.ip.v4.uint8, b + 12, 4); return ip4; } bool is_loopback(const IP &ip) { if (net_family_is_ipv4(ip.family)) { return ip.ip.v4.uint32 == net_htonl(0x7F000001); } if (net_family_is_ipv6(ip.family)) { const uint8_t *b = ip.ip.v6.uint8; for (int i = 0; i < 15; ++i) { if (b[i] != 0) { return false; } } return b[15] == 1; } return false; } void NetworkUniverse::process_events(uint64_t current_time_ms) { while (true) { Packet p; std::vector tcp_targets; FakeUdpSocket *udp_target = nullptr; bool has_packet = false; { std::lock_guard lock(mutex_); if (!event_queue_.empty()) { const Packet &top = event_queue_.top(); if (verbose_) { std::cerr << "[NetworkUniverse] Peek packet: time=" << top.delivery_time << " current=" << current_time_ms << " tcp=" << top.is_tcp << std::endl; } } if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) { p = event_queue_.top(); event_queue_.pop(); has_packet = true; if (verbose_) { Ip_Ntoa from_str, to_str; net_ip_ntoa(&p.from.ip, &from_str); net_ip_ntoa(&p.to.ip, &to_str); std::cerr << "[NetworkUniverse] Processing packet #" << p.sequence_number << " from " << from_str.buf << ":" << net_ntohs(p.from.port) << " to " << to_str.buf << ":" << net_ntohs(p.to.port) << " (TCP=" << (p.is_tcp ? "true" : "false"); if (p.is_tcp) { std::cerr << " Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq << " Ack=" << p.ack; } std::cerr << " Size=" << p.data.size() << ")" << std::endl; } IP target_ip = p.to.ip; if (p.is_tcp) { if (is_loopback(target_ip) && tcp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) { if (verbose_) { std::cerr << "[NetworkUniverse] Loopback packet to " << static_cast(target_ip.ip.v4.uint8[3]) << " redirected to " << static_cast(p.from.ip.ip.v4.uint8[3]) << std::endl; } target_ip = p.from.ip; } auto range = tcp_bindings_.equal_range({target_ip, net_ntohs(p.to.port)}); FakeTcpSocket *listen_match = nullptr; for (auto it = range.first; it != range.second; ++it) { FakeTcpSocket *s = it->second; if (s->state() == FakeTcpSocket::LISTEN) { listen_match = s; } else { const IP_Port &remote = s->remote_addr(); if (net_ntohs(p.from.port) == net_ntohs(remote.port)) { if (ip_equal(&p.from.ip, &remote.ip) || (is_loopback(p.from.ip) && ip_equal(&remote.ip, &target_ip)) || (is_loopback(remote.ip) && ip_equal(&p.from.ip, &target_ip))) { tcp_targets.push_back(s); } } } } if (listen_match && (p.tcp_flags & 0x02)) { tcp_targets.push_back(listen_match); } if (verbose_) { std::cerr << "[NetworkUniverse] Routing TCP to " << static_cast(target_ip.ip.v4.uint8[0]) << "." << static_cast(target_ip.ip.v4.uint8[3]) << ":" << net_ntohs(p.to.port) << ". Targets found: " << tcp_targets.size() << std::endl; } if (tcp_targets.empty()) { if (verbose_) { std::cerr << "[NetworkUniverse] WARNING: No TCP targets for " << static_cast(target_ip.ip.v4.uint8[0]) << "." << static_cast(target_ip.ip.v4.uint8[3]) << ":" << net_ntohs(p.to.port) << std::endl; } } } else { if (is_loopback(target_ip) && udp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) { target_ip = p.from.ip; } if (udp_bindings_.count({target_ip, net_ntohs(p.to.port)})) { udp_target = udp_bindings_[{target_ip, net_ntohs(p.to.port)}]; } else if (is_ipv4_mapped(target_ip)) { IP ip4 = extract_ipv4(target_ip); if (udp_bindings_.count({ip4, net_ntohs(p.to.port)})) { udp_target = udp_bindings_[{ip4, net_ntohs(p.to.port)}]; } } } } } if (!has_packet) { break; } if (p.is_tcp) { for (auto *it : tcp_targets) { if (it->handle_packet(p)) { break; } } } else { if (udp_target) { udp_target->push_packet(std::move(p.data), p.from); } } } } void NetworkUniverse::set_latency(uint64_t ms) { global_latency_ms_ = ms; } void NetworkUniverse::set_verbose(bool verbose) { verbose_ = verbose; } bool NetworkUniverse::is_verbose() const { return verbose_; } void NetworkUniverse::add_filter(PacketFilter filter) { filters_.push_back(std::move(filter)); } void NetworkUniverse::add_observer(PacketSink sink) { observers_.push_back(std::move(sink)); } uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start) { std::lock_guard lock(mutex_); for (uint16_t port = start; port < 65535; ++port) { if (!udp_bindings_.count({ip, port}) && !tcp_bindings_.count({ip, port})) return port; } return 0; } } // namespace tox::test