diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index 462700a1..754a3bc0 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -83,6 +83,7 @@ IPStackSimulator::IPStackSimulator( game_server(game_server), proxy_server(proxy_server), state(state), + proxy_destination_address(0), 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); @@ -123,7 +124,7 @@ void IPStackSimulator::add_socket(int fd) { uint32_t IPStackSimulator::connect_address_for_remote_address(uint32_t remote_addr) { - // Use and address not on the same subnet as the client, so that PSO Plus and + // Use an address not on the same subnet as the client, so that PSO Plus and // Episode III will think they're talking to a remote network and won't reject // the connection. if ((remote_addr & 0xFF000000) != 0x23000000) { @@ -420,7 +421,9 @@ 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); + uint32_t resolved_address = this->proxy_destination_address + ? this->proxy_destination_address + : this->connect_address_for_remote_address(c->ipv4_addr); string r_data = DNSServer::response_for_query( fi.payload, fi.payload_size, resolved_address); @@ -743,13 +746,6 @@ void IPStackSimulator::open_server_connection( throw logic_error("server connection is already open"); } - const PortConfiguration* port_config; - try { - port_config = &this->state->numbered_port_configuration.at(conn.server_port); - } catch (const out_of_range&) { - throw logic_error("client connected to port missing from configuration"); - } - struct bufferevent* bevs[2]; bufferevent_pair_new(this->base.get(), 0, bevs); @@ -762,16 +758,26 @@ void IPStackSimulator::open_server_connection( // Link the client to the server - the server sees this as a normal TCP // connection and treats it as if the client connected to one of its listening // sockets + string conn_str = this->str_for_tcp_connection(c, conn); if (this->game_server.get()) { + const PortConfiguration* port_config; + try { + port_config = &this->state->numbered_port_configuration.at(conn.server_port); + } catch (const out_of_range&) { + bufferevent_free(bevs[1]); + throw logic_error("client connected to port missing from configuration"); + } + this->game_server->connect_client(bevs[1], c->ipv4_addr, conn.client_port, port_config->version, port_config->behavior); - } else if (this->proxy_server.get()) { - this->proxy_server->connect_client(bevs[1]); - } + log(INFO, "[IPStackSimulator] Connected TCP connection %s to game server", + conn_str.c_str()); - string conn_str = this->str_for_tcp_connection(c, conn); - log(INFO, "[IPStackSimulator] Connected TCP connection %s to game server", - conn_str.c_str()); + } else if (this->proxy_server.get()) { + this->proxy_server->connect_client(bevs[1], conn.server_addr, conn.server_port); + log(INFO, "[IPStackSimulator] Connected TCP connection %s to proxy server", + conn_str.c_str()); + } } void IPStackSimulator::send_pending_push_frame( diff --git a/src/IPStackSimulator.hh b/src/IPStackSimulator.hh index 87936b03..24911d61 100644 --- a/src/IPStackSimulator.hh +++ b/src/IPStackSimulator.hh @@ -27,6 +27,10 @@ public: void listen(int port); void add_socket(int fd); + inline void set_proxy_destination_address(uint32_t addr) { + this->proxy_destination_address = addr; + } + static uint32_t connect_address_for_remote_address(uint32_t remote_addr); private: @@ -34,6 +38,7 @@ private: std::shared_ptr game_server; std::shared_ptr proxy_server; std::shared_ptr state; + uint32_t proxy_destination_address; using unique_listener = std::unique_ptr; using unique_bufferevent = std::unique_ptr; diff --git a/src/Main.cc b/src/Main.cc index 6b80dfb3..c28b5a16 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -261,12 +261,17 @@ int main(int argc, char* argv[]) { shared_ptr proxy_server; shared_ptr game_server; + uint32_t proxy_destination_address = 0; if (proxy_port) { log(INFO, "Starting proxy server"); sockaddr_storage proxy_destination_ss = make_sockaddr_storage( proxy_hostname, proxy_port).first; - proxy_server.reset(new ProxyServer(base, proxy_destination_ss, - proxy_version)); + if (proxy_destination_ss.ss_family != AF_INET) { + throw runtime_error("proxy destination address is not IPv4"); + } + proxy_destination_address = ntohl( + reinterpret_cast(&proxy_destination_ss)->sin_addr.s_addr); + proxy_server.reset(new ProxyServer(base, proxy_destination_ss, proxy_version)); proxy_server->listen(proxy_port); if (proxy_version == GameVersion::BB) { proxy_server->listen(proxy_port + 1); @@ -297,6 +302,7 @@ int main(int argc, char* argv[]) { log(INFO, "Starting IP stack simulator"); ip_stack_simulator.reset(new IPStackSimulator( base, game_server, proxy_server, state)); + ip_stack_simulator->set_proxy_destination_address(proxy_destination_address); for (const auto& it : state->ip_stack_addresses) { auto netloc = parse_netloc(it); ip_stack_simulator->listen(netloc.first, netloc.second); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index ded088ff..f5fb7428 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -44,6 +44,7 @@ ProxyServer::ProxyServer( client_bev(nullptr, flush_and_free_bufferevent), server_bev(nullptr, flush_and_free_bufferevent), next_destination(initial_destination), + default_destination(initial_destination), version(version), header_size((version == GameVersion::BB) ? 8 : 4), save_quests(false) { @@ -150,7 +151,8 @@ void ProxyServer::on_listen_accept(struct evconnlistener*, evutil_socket_t fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS)); } -void ProxyServer::connect_client(struct bufferevent* bev) { +void ProxyServer::connect_client( + struct bufferevent* bev, uint32_t server_ipv4_addr, uint16_t server_port) { if (this->client_bev.get()) { log(WARNING, "[ProxyServer] Ignoring client virtual connection because client already exists"); bufferevent_flush(bev, EV_WRITE, BEV_FINISHED); @@ -158,10 +160,12 @@ void ProxyServer::connect_client(struct bufferevent* bev) { } log(INFO, "[ProxyServer] Client connected on virtual connection %p", bev); - this->on_client_connect(bev); + + this->on_client_connect(bev, server_ipv4_addr, server_port); } -void ProxyServer::on_client_connect(struct bufferevent* bev) { +void ProxyServer::on_client_connect(struct bufferevent* bev, + uint32_t server_ipv4_addr, uint16_t server_port) { this->client_bev.reset(bev); bufferevent_setcb(this->client_bev.get(), @@ -173,22 +177,26 @@ void ProxyServer::on_client_connect(struct bufferevent* bev) { this->server_bev.reset(bufferevent_socket_new(this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS)); - // TODO: figure out why this copy is necessary... shouldn't we just be able to - // use the sockaddr_storage directly? - const struct sockaddr_in* sin_ss = reinterpret_cast(&this->next_destination); - if (sin_ss->sin_family != AF_INET) { - throw logic_error("ss not AF_INET"); + struct sockaddr_storage local_ss; + struct sockaddr_in* local_sin = reinterpret_cast(&local_ss); + memset(local_sin, 0, sizeof(*local_sin)); + local_sin->sin_family = AF_INET; + if (server_ipv4_addr && server_port) { + local_sin->sin_port = htons(server_port); + local_sin->sin_addr.s_addr = htonl(server_ipv4_addr); + } else { + const struct sockaddr_in* dest_sin = reinterpret_cast(&this->next_destination); + if (dest_sin->sin_family != AF_INET) { + throw logic_error("ss not AF_INET"); + } + local_sin->sin_port = dest_sin->sin_port; + local_sin->sin_addr.s_addr = dest_sin->sin_addr.s_addr; } - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = sin_ss->sin_port; - sin.sin_addr.s_addr = sin_ss->sin_addr.s_addr; - string netloc_str = render_sockaddr_storage(this->next_destination); + string netloc_str = render_sockaddr_storage(local_ss); log(INFO, "[ProxyServer] Connecting to %s", netloc_str.c_str()); if (bufferevent_socket_connect(this->server_bev.get(), - reinterpret_cast(&sin), sizeof(sin)) != 0) { + reinterpret_cast(local_sin), sizeof(*local_sin)) != 0) { throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR())); } bufferevent_setcb(this->server_bev.get(), @@ -410,13 +418,14 @@ void ProxyServer::receive_and_process_commands(bool from_server) { if (!dest_bev) { log(WARNING, "[ProxyServer] Received reconnect command with no destination present"); } else { - struct sockaddr_storage sockname_ss; - socklen_t len = sizeof(sockname_ss); + // If the client is on a virtual connection (fd < 0), we don't need + // to do anything - we assume the client will connect again via a + // virtual connection, and we can just use the destination + // address/port from their connection request. int fd = bufferevent_getfd(dest_bev); - if (fd < 0) { // virtual connection - args->address = 0x23232323; // TODO: apply the different-network logic here too - args->port = 9000; - } else { + if (fd >= 0) { + struct sockaddr_storage sockname_ss; + socklen_t len = sizeof(sockname_ss); getsockname(fd, reinterpret_cast(&sockname_ss), &len); if (sockname_ss.ss_family != AF_INET) { throw logic_error("existing connection is not ipv4"); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index e510b466..69398311 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -20,13 +20,16 @@ public: ProxyServer() = delete; ProxyServer(const ProxyServer&) = delete; ProxyServer(ProxyServer&&) = delete; - ProxyServer(std::shared_ptr base, - const struct sockaddr_storage& initial_destination, GameVersion version); + ProxyServer( + std::shared_ptr base, + const struct sockaddr_storage& initial_destination, + GameVersion version); virtual ~ProxyServer() = default; void listen(int port); - void connect_client(struct bufferevent* bev); + void connect_client(struct bufferevent* bev, uint32_t server_ipv4_addr, + uint16_t server_port); void send_to_client(const std::string& data); void send_to_server(const std::string& data); @@ -39,6 +42,7 @@ private: std::unique_ptr client_bev; std::unique_ptr server_bev; struct sockaddr_storage next_destination; + struct sockaddr_storage default_destination; int listen_port; GameVersion version; @@ -84,7 +88,8 @@ private: void on_server_input(struct bufferevent* bev); void on_server_error(struct bufferevent* bev, short events); - void on_client_connect(struct bufferevent* bev); + void on_client_connect(struct bufferevent* bev, + uint32_t server_ipv4_addr = 0, uint16_t server_port = 0); size_t get_size_field(const PSOCommandHeader* header); size_t get_command_field(const PSOCommandHeader* header);