diff --git a/README.md b/README.md index 80252985..dcec5b6f 100644 --- a/README.md +++ b/README.md @@ -368,12 +368,7 @@ If you're using the TAP BBA type, you'll have to set PSO's network settings appr If you're using a version of Dolphin with tapserver support, you can make it connect to a newserv instance running on the same machine via the tapserver interface. You do not need to install or run tapserver. To do this: 1. Set Dolphin's BBA type to tapserver (Config -> GameCube -> SP1). 2. Enable newserv's IP stack simulator according to the comments in config.json and start newserv. -3. In PSO, you have to configure the network settings manually (DHCP doesn't work), but the actual values don't matter as long as they're valid IP addresses. Example values: - - IP address: `10.0.1.5` - - Subnet mask: `255.255.255.0` - - Default gateway: `10.0.1.1` - - DNS server address 1: `10.0.1.1` - - Leave everything else blank +3. In PSO's network settings, enable DHCP ("Automatically obtain an IP address"), set DNS server address to "Automatic", and leave DHCP Hostname as "Not set". Leave the proxy server settings blank. 4. Start an online game. ### Connecting external clients diff --git a/src/IPFrameInfo.hh b/src/IPFrameInfo.hh index 85df9b6d..8ce47107 100644 --- a/src/IPFrameInfo.hh +++ b/src/IPFrameInfo.hh @@ -4,9 +4,11 @@ #include +#include "Text.hh" + struct EthernetHeader { - uint8_t dest_mac[6]; - uint8_t src_mac[6]; + parray dest_mac; + parray src_mac; be_uint16_t protocol; } __attribute__((packed)); @@ -61,6 +63,24 @@ struct TCPHeader { be_uint16_t urgent_ptr; } __attribute__((packed)); +struct DHCPHeader { + uint8_t opcode = 0; + uint8_t hardware_type = 1; // 1 = Ethernet + uint8_t hardware_address_length = 6; // 6 for Ethernet + uint8_t hops = 0; + be_uint32_t transaction_id = 0; + be_uint16_t seconds_elapsed = 0; + be_uint16_t flags = 0; + be_uint32_t client_ip_address = 0; + be_uint32_t your_ip_address = 0; + be_uint32_t server_ip_address = 0; + be_uint32_t gateway_ip_address = 0; + parray client_hardware_address; + parray unused_bootp_legacy; + be_uint32_t magic = 0x63825363; + // Options follow here, terminated with FF +} __attribute__((packed)); + struct FrameInfo { // This is always valid const EthernetHeader* ether; diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index 565a89fd..350ef047 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -67,8 +67,8 @@ IPStackSimulator::IPStackSimulator( : base(base), state(state), pcap_text_log_file(state->ip_stack_debug ? fopen("IPStackSimulator-Log.txt", "wt") : nullptr) { - memset(this->host_mac_address_bytes, 0x90, 6); - memset(this->broadcast_mac_address_bytes, 0xFF, 6); + this->host_mac_address_bytes.clear(0x90); + this->broadcast_mac_address_bytes.clear(0xFF); } IPStackSimulator::~IPStackSimulator() { @@ -115,7 +115,7 @@ uint32_t IPStackSimulator::connect_address_for_remote_address(uint32_t remote_ad IPStackSimulator::IPClient::IPClient(struct bufferevent* bev) : bev(bev, bufferevent_free), ipv4_addr(0) { - memset(this->mac_addr, 0, 6); + this->mac_addr.clear(0); } static void flush_and_free_bufferevent(struct bufferevent* bev) { @@ -233,16 +233,16 @@ void IPStackSimulator::on_client_error(struct bufferevent* bev, void IPStackSimulator::on_client_frame( shared_ptr c, const string& frame) { - if (ip_stack_simulator_log.info("Virtual network sent frame")) { + if (ip_stack_simulator_log.debug("Virtual network sent frame")) { print_data(stderr, frame); fputc('\n', stderr); } this->log_frame(frame); FrameInfo fi(frame); - if (ip_stack_simulator_log.should_log(LogLevel::INFO)) { + if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) { string fi_header = fi.header_str(); - ip_stack_simulator_log.info("Frame header: %s", fi_header.c_str()); + ip_stack_simulator_log.debug("Frame header: %s", fi_header.c_str()); } if (fi.arp) { @@ -255,10 +255,14 @@ void IPStackSimulator::on_client_frame( "IPv4 header checksum is incorrect (%04hX expected, %04hX received)", expected_ipv4_checksum, fi.ipv4->checksum.load())); } - if (memcmp(fi.ether->src_mac, c->mac_addr, 6)) { + + // Populate the client's addresses if needed + if (c->mac_addr.is_filled_with(0)) { + c->mac_addr = fi.ether->src_mac; + } else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) { throw runtime_error("client sent IPv4 packet from different MAC address"); } - if (fi.ipv4->src_addr != c->ipv4_addr) { + if ((fi.ipv4->src_addr != c->ipv4_addr) && (fi.ipv4->src_addr != 0)) { throw runtime_error("client sent IPv4 packet from different IPv4 address"); } @@ -301,18 +305,14 @@ void IPStackSimulator::on_client_arp_frame( throw runtime_error("ARP payload too small"); } - // Populate the client's addresses if needed - if (!memcmp(c->mac_addr, "\0\0\0\0\0\0", 6)) { - memcpy(c->mac_addr, fi.ether->src_mac, 6); - } if (c->ipv4_addr == 0) { c->ipv4_addr = *reinterpret_cast( reinterpret_cast(fi.payload) + 6); } EthernetHeader r_ether; - memcpy(r_ether.dest_mac, fi.ether->src_mac, 6); - memcpy(r_ether.src_mac, this->host_mac_address_bytes, 6); + r_ether.dest_mac = fi.ether->src_mac; + r_ether.src_mac = this->host_mac_address_bytes; r_ether.protocol = fi.ether->protocol; ARPHeader r_arp; @@ -336,7 +336,7 @@ void IPStackSimulator::on_client_arp_frame( const char* payload_bytes = reinterpret_cast(fi.payload); uint8_t r_payload[20]; - memcpy(&r_payload[0], this->host_mac_address_bytes, 6); + memcpy(&r_payload[0], this->host_mac_address_bytes.data(), 6); memcpy(&r_payload[6], payload_bytes + 16, 4); memcpy(&r_payload[10], payload_bytes, 10); @@ -348,7 +348,7 @@ void IPStackSimulator::on_client_arp_frame( evbuffer_add(out_buf, &r_arp, sizeof(r_arp)); evbuffer_add(out_buf, r_payload, sizeof(r_payload)); - ip_stack_simulator_log.info("Sending ARP response"); + ip_stack_simulator_log.debug("Sending ARP response"); if (this->pcap_text_log_file) { StringWriter w; @@ -361,17 +361,13 @@ void IPStackSimulator::on_client_arp_frame( void IPStackSimulator::on_client_udp_frame( shared_ptr c, const FrameInfo& fi) { - // We only implement the DNS server here - if (fi.udp->dest_port != 53) { - throw runtime_error("UDP packet is not DNS"); - } - if (fi.payload_size < 0x0C) { - throw runtime_error("DNS payload too small"); - } + // We only implement DHCP and newserv's DNS server here + // Every received UDP packet will elicit exactly one UDP response from + // newserv, so we prepare the response headers in advance EthernetHeader r_ether; - memcpy(r_ether.dest_mac, fi.ether->src_mac, 6); - memcpy(r_ether.src_mac, this->host_mac_address_bytes, 6); + r_ether.dest_mac = fi.ether->src_mac; + r_ether.src_mac = this->host_mac_address_bytes; r_ether.protocol = fi.ether->protocol; IPv4Header r_ipv4; @@ -392,10 +388,126 @@ void IPStackSimulator::on_client_udp_frame( // r_udp.size filled in later // r_udp.checksum filled in later - uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr); + string r_data; + if (fi.udp->dest_port == 67) { // DHCP + StringReader r(fi.payload, fi.payload_size); + const auto& dhcp = r.get(); + if (dhcp.hardware_type != 1) { + throw runtime_error("unknown DHCP hardware type"); + } + if (dhcp.hardware_address_length != 6) { + throw runtime_error("unknown DHCP hardware address length"); + } + if (dhcp.magic != 0x63825363) { + throw runtime_error("incorrect DHCP magic cookie"); + } - string r_data = DNSServer::response_for_query( - fi.payload, fi.payload_size, resolved_address); + unordered_map option_data; + for (;;) { + uint8_t option = r.get_u8(); + if (option == 0xFF) { + break; + } + uint8_t size = r.get_u8(); + option_data.emplace(option, r.read(size)); + } + + if (dhcp.opcode == 1) { // Request + uint8_t command = 0; + try { + command = option_data.at(53).at(0); + } catch (const out_of_range&) { + throw runtime_error("client did not send a DHCP command option"); + } + + // Populate the client's addresses + c->mac_addr = dhcp.client_hardware_address.data(); + c->ipv4_addr = 0x0A000105; // 10.0.1.5 + // In this case, the client doesn't know its IPv4 address or ours yet, + // so we overwrite the existing fields with the appropriate addresses. + r_ipv4.src_addr = 0x0A000101; // 10.0.1.1 + r_ipv4.dest_addr = c->ipv4_addr; + + if ((command != 1) && (command != 3)) { + throw runtime_error("client sent unknown DHCP command option"); + } + + StringWriter w; + w.put(DHCPHeader{ + .opcode = 2, // Response + .hardware_type = 1, // Ethernet + .hardware_address_length = 6, // Ethernet + .hops = 0, + .transaction_id = dhcp.transaction_id, + .seconds_elapsed = 0, + .flags = 0, + .client_ip_address = 0, + .your_ip_address = r_ipv4.dest_addr, + .server_ip_address = r_ipv4.src_addr, + .gateway_ip_address = 0, + .client_hardware_address = c->mac_addr, + .magic = 0x63825363, + }); + // DHCP message type option + w.put_u8(53); + w.put_u8(1); + w.put_u8(static_cast((command == 3) ? 5 : 2)); // Offer or ack + // DHCP server ID option + w.put_u8(54); + w.put_u8(4); + w.put_u32b(0x0A000101); // 10.0.1.1 + // Lease time option + w.put_u8(51); + w.put_u8(4); + w.put_u32b(60 * 60 * 24 * 7); // 1 week + // Renewal time option + w.put_u8(58); + w.put_u8(4); + w.put_u32b(60 * 60 * 24 * 7); // 1 week + // Rebind time option + w.put_u8(59); + w.put_u8(4); + w.put_u32b(60 * 60 * 24 * 7); // 1 week + // Subnet mask option + w.put_u8(1); + w.put_u8(4); + w.put_u32b(0xFFFFFF00); // 255.255.255.0 + // Broadcast IP option + w.put_u8(28); + w.put_u8(4); + w.put_u32b(c->ipv4_addr | 0x000000FF); + // DNS server option + w.put_u8(6); + w.put_u8(4); + w.put_u32b(0x0A000101); // 10.0.1.1 + // Domain name option + w.put_u8(15); + w.put_u8(7); + w.write("newserv"); + // Default gateway option + w.put_u8(3); + w.put_u8(4); + w.put_u32b(0x0A000101); // 10.0.1.1 + // End option list + w.put_u8(0xFF); + + r_data = std::move(w.str()); + + } else { + throw runtime_error("unknown DHCP command"); + } + + } else if (fi.udp->dest_port == 53) { // DNS + if (fi.payload_size < 0x0C) { + throw runtime_error("DNS payload too small"); + } + + uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr); + r_data = DNSServer::response_for_query(fi.payload, fi.payload_size, resolved_address); + + } else { // Not DHCP or DNS + throw runtime_error("UDP packet is not DHCP or DNS"); + } r_ipv4.size = sizeof(IPv4Header) + sizeof(UDPHeader) + r_data.size(); r_udp.size = sizeof(UDPHeader) + r_data.size(); @@ -405,9 +517,9 @@ void IPStackSimulator::on_client_udp_frame( struct evbuffer* out_buf = bufferevent_get_output(c->bev.get()); - if (ip_stack_simulator_log.should_log(LogLevel::INFO)) { + if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) { string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port); - ip_stack_simulator_log.info("Sending DNS response to %s", remote_str.c_str()); + ip_stack_simulator_log.debug("Sending UDP response to %s", remote_str.c_str()); print_data(stderr, r_data); } @@ -451,7 +563,7 @@ uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const FrameInfo& fi) { void IPStackSimulator::on_client_tcp_frame( shared_ptr c, const FrameInfo& fi) { - ip_stack_simulator_log.info("Virtual network sent TCP frame (seq=%08" PRIX32 ", ack=%08" PRIX32 ")", + ip_stack_simulator_log.debug("Virtual network sent TCP frame (seq=%08" PRIX32 ", ack=%08" PRIX32 ")", fi.tcp->seq_num.load(), fi.tcp->ack_num.load()); if (fi.tcp->flags & (TCPHeader::Flag::NS | TCPHeader::Flag::CWR | TCPHeader::Flag::ECE | TCPHeader::Flag::URG)) { @@ -541,13 +653,13 @@ void IPStackSimulator::on_client_tcp_frame( // TODO: We should check the syn/ack numbers here instead of just assuming // they're correct conn_str = this->str_for_tcp_connection(c, conn); - ip_stack_simulator_log.info("Client resent SYN for TCP connection %s", + ip_stack_simulator_log.debug("Client resent SYN for TCP connection %s", conn_str.c_str()); } // Send a SYN+ACK (send_tcp_frame always adds the ACK flag) this->send_tcp_frame(c, conn, TCPHeader::Flag::SYN); - ip_stack_simulator_log.info("Sent SYN+ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")", + ip_stack_simulator_log.debug("Sent SYN+ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")", conn_str.c_str(), conn.acked_server_seq, conn.next_client_seq); } else { @@ -562,7 +674,7 @@ void IPStackSimulator::on_client_tcp_frame( bool conn_valid = true; if (fi.tcp->flags & TCPHeader::Flag::ACK) { - ip_stack_simulator_log.info("Client sent ACK %08" PRIX32, fi.tcp->ack_num.load()); + ip_stack_simulator_log.debug("Client sent ACK %08" PRIX32, fi.tcp->ack_num.load()); if (conn->awaiting_first_ack) { if (fi.tcp->ack_num != conn->acked_server_seq + 1) { throw runtime_error("first ack_num was not acked_server_seq + 1"); @@ -572,7 +684,7 @@ void IPStackSimulator::on_client_tcp_frame( } else { if (seq_num_greater(fi.tcp->ack_num, conn->acked_server_seq)) { - ip_stack_simulator_log.info("Advancing acked_server_seq from %08" PRIX32, conn->acked_server_seq); + ip_stack_simulator_log.debug("Advancing acked_server_seq from %08" PRIX32, conn->acked_server_seq); uint32_t ack_delta = fi.tcp->ack_num - conn->acked_server_seq; size_t pending_bytes = evbuffer_get_length(conn->pending_data.get()); if (pending_bytes < ack_delta) { @@ -584,7 +696,7 @@ void IPStackSimulator::on_client_tcp_frame( conn->resend_push_usecs = DEFAULT_RESEND_PUSH_USECS; conn->next_push_max_frame_size = conn->max_frame_size; - ip_stack_simulator_log.info("Removed %08" PRIX32 " bytes from pending buffer and advanced acked_server_seq to %08" PRIX32, + ip_stack_simulator_log.debug("Removed %08" PRIX32 " bytes from pending buffer and advanced acked_server_seq to %08" PRIX32, ack_delta, conn->acked_server_seq); } else if (seq_num_less(fi.tcp->ack_num, conn->acked_server_seq)) { @@ -662,10 +774,10 @@ void IPStackSimulator::on_client_tcp_frame( bool was_logged; if (payload_skip_bytes) { - was_logged = ip_stack_simulator_log.info("Client sent data on TCP connection %s, overlapping existing ack'ed data (0x%zX bytes ignored)", + was_logged = ip_stack_simulator_log.debug("Client sent data on TCP connection %s, overlapping existing ack'ed data (0x%zX bytes ignored)", conn_str.c_str(), payload_skip_bytes); } else { - was_logged = ip_stack_simulator_log.info("Client sent data on TCP connection %s", + was_logged = ip_stack_simulator_log.debug("Client sent data on TCP connection %s", conn_str.c_str()); } if (was_logged) { @@ -688,7 +800,7 @@ void IPStackSimulator::on_client_tcp_frame( // Send an ACK this->send_tcp_frame(c, *conn); - ip_stack_simulator_log.info("Sent PSH ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ", bytes_received=0x%zX)", + ip_stack_simulator_log.debug("Sent PSH ACK on %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ", bytes_received=0x%zX)", conn_str.c_str(), conn->acked_server_seq, conn->next_client_seq, conn->bytes_received); } @@ -758,7 +870,7 @@ void IPStackSimulator::send_pending_push_frame( size_t bytes_to_send = min(pending_bytes, conn.next_push_max_frame_size); - ip_stack_simulator_log.info("Sending PSH frame with seq_num %08" PRIX32 ", 0x%zX/0x%zX data bytes", + ip_stack_simulator_log.debug("Sending PSH frame with seq_num %08" PRIX32 ", 0x%zX/0x%zX data bytes", conn.acked_server_seq, bytes_to_send, pending_bytes); this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn.pending_data.get(), @@ -790,8 +902,8 @@ void IPStackSimulator::send_tcp_frame( } EthernetHeader ether; - memcpy(ether.dest_mac, c->mac_addr, 6); - memcpy(ether.src_mac, this->host_mac_address_bytes, 6); + ether.dest_mac = c->mac_addr; + ether.src_mac = this->host_mac_address_bytes; ether.protocol = 0x0800; // IPv4 IPv4Header ipv4; @@ -870,7 +982,7 @@ void IPStackSimulator::dispatch_on_server_input(struct bufferevent*, void* ctx) void IPStackSimulator::on_server_input(shared_ptr c, IPClient::TCPConnection& conn) { struct evbuffer* buf = bufferevent_get_input(conn.server_bev.get()); - ip_stack_simulator_log.info("Server input event: 0x%zX bytes to read", + ip_stack_simulator_log.debug("Server input event: 0x%zX bytes to read", evbuffer_get_length(buf)); evbuffer_add_buffer(conn.pending_data.get(), buf); @@ -904,8 +1016,7 @@ void IPStackSimulator::on_server_error( // Delete the connection object (this also flushes and frees the server // virtual connection bufferevent) string conn_str = this->str_for_tcp_connection(c, conn); - ip_stack_simulator_log.info("Server closed TCP connection %s", - conn_str.c_str()); + ip_stack_simulator_log.info("Server closed TCP connection %s", conn_str.c_str()); c->tcp_connections.erase(this->tcp_conn_key_for_connection(conn)); } } diff --git a/src/IPStackSimulator.hh b/src/IPStackSimulator.hh index 4ff17df6..db0c5214 100644 --- a/src/IPStackSimulator.hh +++ b/src/IPStackSimulator.hh @@ -10,6 +10,7 @@ #include "ProxyServer.hh" #include "Server.hh" #include "ServerState.hh" +#include "Text.hh" class IPStackSimulator { public: @@ -38,7 +39,7 @@ private: IPStackSimulator* sim; unique_bufferevent bev; - uint8_t mac_addr[6]; + parray mac_addr; uint32_t ipv4_addr; struct TCPConnection { @@ -78,8 +79,8 @@ private: std::unordered_set listeners; std::unordered_map> bev_to_client; - uint8_t host_mac_address_bytes[6]; - uint8_t broadcast_mac_address_bytes[6]; + parray host_mac_address_bytes; + parray broadcast_mac_address_bytes; FILE* pcap_text_log_file; @@ -91,19 +92,19 @@ private: static std::string str_for_ipv4_netloc(uint32_t addr, uint16_t port); static std::string str_for_tcp_connection(std::shared_ptr c, - const IPClient::TCPConnection& conn); + const IPClient::TCPConnection& conn); static void dispatch_on_listen_accept(struct evconnlistener* listener, - evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); + evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, - struct sockaddr* address, int socklen); + struct sockaddr* address, int socklen); static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx); void on_listen_error(struct evconnlistener* listener); static void dispatch_on_client_input(struct bufferevent* bev, void* ctx); void on_client_input(struct bufferevent* bev); static void dispatch_on_client_error(struct bufferevent* bev, short events, - void* ctx); + void* ctx); void on_client_error(struct bufferevent* bev, short events); void on_client_frame(std::shared_ptr c, const std::string& frame); @@ -112,13 +113,13 @@ private: void on_client_tcp_frame(std::shared_ptr c, const FrameInfo& fi); static void dispatch_on_resend_push(evutil_socket_t fd, short events, - void* ctx); + void* ctx); void on_resend_push(std::shared_ptr c, IPClient::TCPConnection& conn); static void dispatch_on_server_input(struct bufferevent* bev, void* ctx); void on_server_input(std::shared_ptr c, IPClient::TCPConnection& conn); static void dispatch_on_server_error(struct bufferevent* bev, short events, - void* ctx); + void* ctx); void on_server_error(std::shared_ptr c, IPClient::TCPConnection& conn, short events); void send_pending_push_frame( diff --git a/src/ServerState.cc b/src/ServerState.cc index e40aa03b..e58ff533 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -509,7 +509,11 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->dns_server_port = json.get_int("DNSServerPort", this->dns_server_port); try { for (const auto& item : json.at("IPStackListen").as_list()) { - this->ip_stack_addresses.emplace_back(item->as_string()); + if (item->is_int()) { + this->ip_stack_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int())); + } else { + this->ip_stack_addresses.emplace_back(item->as_string()); + } } } catch (const out_of_range&) { } diff --git a/system/config.example.json b/system/config.example.json index 5d3827da..07d6d968 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -190,9 +190,8 @@ "FunctionCompiler": "INFO", // IP stack simulator messages describe clients connecting and disconnecting // via the IP stack interface, and errors that occur at the simulated - // network level within the simulator. This log is fairly verbose at the - // info level, so by default we suppress those messages. - "IPStackSimulator": "WARNING", + // network level within the simulator. + "IPStackSimulator": "INFO", // Lobby messages describe creation and deletion of lobbies and games, as // well as item tracking events within games. On Episode 3, debug messages // during battles go to this stream as well; use "DEBUG" here to see them.