add IP stack simulator

This commit is contained in:
Martin Michelsen
2022-03-07 19:45:18 -08:00
parent b2437b085c
commit 951b6085ab
19 changed files with 1745 additions and 96 deletions
+2
View File
@@ -60,6 +60,8 @@ add_executable(newserv
src/Shell.cc
src/ServerShell.cc
src/ProxyShell.cc
src/IPFrameInfo.cc
src/IPStackSimulator.cc
src/Main.cc
)
target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR})
+13 -9
View File
@@ -27,7 +27,7 @@ This project is primarily for my own nostalgia. Feel free to peruse if you'd lik
Currently this code should build on macOS and Ubuntu. It might build on other Linux flavors, but don't expect it to work on Windows at all.
So, you've read all of the above and you want to try it out? Here's what you do:
- Make sure you have CMake and libevent installed (use Homebrew in macOS, or install libevent-dev in Linux).
- Make sure you have CMake and libevent installed.
- Build and install phosg (https://github.com/fuzziqersoftware/phosg).
- Run `cmake . && make`.
- Edit system/config.json to your liking.
@@ -36,17 +36,21 @@ So, you've read all of the above and you want to try it out? Here's what you do:
### Connecting local clients
If you're running PSO on a real GameCube, you can make PSO connect to newserv by changing its default gateway and DNS server addresses to newserv's address.
If you're running PSO on a real GameCube, you can make it connect to newserv by setting its default gateway and DNS server addresses to newserv's address.
If you're emulating PSO GC using Dolphin on Mac OS (like I am), you can make it connect to newserv by doing this:
If you're emulating PSO GC using Dolphin on macOS (like I am), you can make it connect to a newserv instance running on the same machine by doing this:
- Use a build of Dolphin that has tapserver support.
- Install tapserver (https://github.com/fuzziqersoftware/tapserver).
- In PSO, manually configure your network settings as follows: IP address = `192.168.0.200`, subnet mask = `255.255.255.0`, default gateway = `192.168.0.5`, DNS server address 1 = `192.168.0.5`.
- Start Dolphin and newserv.
- Run tapserver according to its instructions.
- Start an online game, and it will connect to your local instance of newserv!
- Enable the IP stack simulator according to the comments in config.json, and start newserv.
- In PSO, you have to configure the network settings manually, 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`
- Start an online game.
If you want to play online on remote servers, newserv also includes a PSO proxy server. Run newserv like `./newserv --proxy-destination=1.1.1.1` (replace the IP address appropriately for the server you want to connect to).
This setup works for all PSO versions, including Plus and Episode III.
If you want to play online on remote servers, newserv also includes a PSO proxy server. Run newserv like `./newserv --proxy-destination=1.1.1.1` (replace the IP address appropriately for the server you want to connect to). Currently this works with PSO PC and GC, but not with BB.
### Connecting external clients
+28 -9
View File
@@ -15,17 +15,36 @@ using namespace std;
Client::Client(struct bufferevent* bev, GameVersion version,
ServerBehavior server_behavior) : version(version),
flags(flags_for_version(version, 0)), bev(bev),
server_behavior(server_behavior), should_disconnect(false),
play_time_begin(now()), last_recv_time(this->play_time_begin),
last_send_time(0), area(0), lobby_id(0), lobby_client_id(0),
lobby_arrow_color(0), next_exp_value(0), infinite_hp(false),
infinite_tp(false), can_chat(true) {
Client::Client(
struct bufferevent* bev,
GameVersion version,
ServerBehavior server_behavior)
: version(version),
flags(flags_for_version(this->version, 0)),
bev(bev),
server_behavior(server_behavior),
should_disconnect(false),
play_time_begin(now()),
last_recv_time(this->play_time_begin),
last_send_time(0),
area(0),
lobby_id(0),
lobby_client_id(0),
lobby_arrow_color(0),
next_exp_value(0),
infinite_hp(false),
infinite_tp(false),
can_chat(true) {
int fd = bufferevent_getfd(this->bev);
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
if (fd < 0) {
this->is_virtual_connection = true;
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} else {
this->is_virtual_connection = false;
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
}
memset(this->name, 0, sizeof(this->name));
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
}
+4 -3
View File
@@ -33,23 +33,24 @@ struct ClientConfigBB {
};
struct Client {
// license & account
// License & account
std::shared_ptr<const License> license;
char16_t name[0x20];
ClientConfigBB config;
GameVersion version;
uint16_t flags;
// encryption
// Encryption
std::unique_ptr<PSOEncryption> crypt_in;
std::unique_ptr<PSOEncryption> crypt_out;
// network
// Network
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
struct bufferevent* bev;
struct sockaddr_storage next_connection_addr;
ServerBehavior server_behavior;
bool is_virtual_connection;
bool should_disconnect;
std::string recv_buffer;
+36 -26
View File
@@ -54,6 +54,31 @@ void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
}
string DNSServer::response_for_query(
const void* vdata, size_t size, uint32_t resolved_address) {
if (size < 0x0C) {
throw invalid_argument("query too small");
}
const char* data = reinterpret_cast<const char*>(vdata);
size_t name_len = strlen(&data[12]) + 1;
be_uint32_t be_resolved_address = resolved_address;
string response;
response.append(data, 2);
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
response.append(&data[12], name_len);
response.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
response.append(reinterpret_cast<const char*>(&be_resolved_address), 4);
return response;
}
string DNSServer::response_for_query(
const string& query, uint32_t resolved_address) {
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
}
void DNSServer::on_receive_message(int fd, short) {
for (;;) {
sockaddr_in remote;
@@ -74,34 +99,19 @@ void DNSServer::on_receive_message(int fd, short) {
} else if (bytes == 0) {
break;
} else { // bytes > 0
} else if (bytes < 0x0C) {
log(WARNING, "[DNSServer] input query too small");
print_data(stderr, input);
} else {
input.resize(bytes);
uint32_t remote_address = bswap32(remote.sin_addr.s_addr);
uint32_t connect_address;
if (is_local_address(remote_address)) {
connect_address = this->local_connect_address;
} else {
connect_address = this->external_connect_address;
}
if (input.size() >= 0x0C) {
string response;
size_t name_len = strlen(input.data() + 0x0C) + 1;
uint32_t connect_address_be = bswap32(connect_address);
response.append(input.substr(0, 2));
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
response.append(input.substr(12, name_len));
response.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
response.append(reinterpret_cast<const char*>(&connect_address_be), 4);
sendto(fd, response.data(), response.size(), 0,
reinterpret_cast<const sockaddr*>(&remote), remote_size);
} else {
log(WARNING, "[DNSServer] input query too small");
print_data(stderr, input);
}
uint32_t connect_address = is_local_address(remote_address)
? this->local_connect_address
: this->external_connect_address;
string response = this->response_for_query(input, connect_address);
sendto(fd, response.data(), response.size(), 0,
reinterpret_cast<const sockaddr*>(&remote), remote_size);
}
}
}
+5
View File
@@ -21,6 +21,11 @@ public:
void listen(int port);
void add_socket(int fd);
static std::string response_for_query(
const void* vdata, size_t size, uint32_t resolved_address);
static std::string response_for_query(
const std::string& query, uint32_t resolved_address);
private:
std::shared_ptr<struct event_base> base;
std::unordered_map<int, std::unique_ptr<struct event, void(*)(struct event*)>> fd_to_receive_event;
+293
View File
@@ -0,0 +1,293 @@
#include "IPFrameInfo.hh"
#include <inttypes.h>
#include <phosg/Strings.hh>
using namespace std;
static inline uint16_t collapse_checksum(uint32_t sum) {
// It's impossible for this to be necessary more than twice: the first
// addition can carry out at most a single bit.
sum = (sum & 0xFFFF) + (sum >> 16);
return (sum & 0xFFFF) + (sum >> 16);
}
FrameInfo::FrameInfo()
: ether(nullptr),
ether_protocol(0),
ipv4(nullptr),
arp(nullptr),
udp(nullptr),
tcp(nullptr),
header_start(nullptr),
payload(nullptr),
total_size(0),
tcp_options_size(0),
payload_size(0) { }
FrameInfo::FrameInfo(const string& data) : FrameInfo(data.data(), data.size()) { }
FrameInfo::FrameInfo(const void* header_start, size_t size)
: ether(nullptr),
ether_protocol(0),
ipv4(nullptr),
arp(nullptr),
udp(nullptr),
tcp(nullptr),
header_start(header_start),
payload(nullptr),
total_size(size),
tcp_options_size(0),
payload_size(size) {
// Parse ethernet header
if (this->payload_size < sizeof(EthernetHeader)) {
throw invalid_argument("frame is too small for ethernet");
}
this->payload_size -= sizeof(EthernetHeader);
this->ether = reinterpret_cast<const EthernetHeader*>(header_start);
this->ether_protocol = this->ether->protocol;
// Figure out the protocol
const be_uint16_t* u16data = reinterpret_cast<const be_uint16_t*>(this->ether + 1);
while ((this->ether_protocol == 0x8100) || (this->ether_protocol == 0x88A8)) {
if (this->payload_size < 4) {
throw invalid_argument("VLAN tags exceed frame size");
}
this->ether_protocol = u16data[1];
u16data += 2;
this->payload_size -= 4;
}
// TODO: Some less-common protocols that we might want to support:
// 0x8035 = RARP
// 0x809B = AppleTalk
// 0x80F3 = AppleTalk ARP
// 0x8137 = IPX
// 0x9000 = loopback
// Parse protocol headers if possible
if (this->ether_protocol == 0x0800) { // IPv4
if (this->payload_size < sizeof(IPv4Header)) {
throw invalid_argument("frame is too small for ipv4 header");
}
this->ipv4 = reinterpret_cast<const IPv4Header*>(u16data);
if (this->payload_size < this->ipv4->size) {
throw invalid_argument("ipv4 header specifies size larger than frame");
}
this->payload_size = this->ipv4->size - sizeof(IPv4Header);
if (this->ipv4->protocol == 0x06) {
if (this->payload_size < sizeof(TCPHeader)) {
throw invalid_argument("frame is too small for tcp4 header");
}
this->tcp = reinterpret_cast<const TCPHeader*>(this->ipv4 + 1);
size_t tcp_header_size = (this->tcp->flags >> 12) * 4;
if (tcp_header_size < sizeof(TCPHeader) || tcp_header_size > this->payload_size) {
throw invalid_argument("frame is too small for tcp4 header with options");
}
this->tcp_options_size = tcp_header_size - sizeof(TCPHeader);
this->payload_size -= tcp_header_size;
this->payload = reinterpret_cast<const uint8_t*>(this->tcp) + tcp_header_size;
} else if (this->ipv4->protocol == 0x11) {
if (this->payload_size < sizeof(UDPHeader)) {
throw invalid_argument("frame is too small for udp4 header");
}
this->payload_size -= sizeof(UDPHeader);
this->udp = reinterpret_cast<const UDPHeader*>(this->ipv4 + 1);
this->payload = this->udp + 1;
} else {
this->payload = this->ipv4 + 1;
}
} else if (this->ether_protocol == 0x0806) { // ARP
if (this->payload_size < sizeof(const ARPHeader)) {
throw invalid_argument("frame is too small for arp header");
}
this->payload_size -= sizeof(ARPHeader);
this->arp = reinterpret_cast<const ARPHeader*>(u16data);
this->payload = this->arp + 1;
} else {
throw runtime_error("unknown protocol");
}
}
string FrameInfo::header_str() const {
if (!this->ether) {
return "<invalid-frame-info>";
}
string ret = string_printf("%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX->%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
this->ether->src_mac[0], this->ether->src_mac[1], this->ether->src_mac[2],
this->ether->src_mac[3], this->ether->src_mac[4], this->ether->src_mac[5],
this->ether->dest_mac[0], this->ether->dest_mac[1], this->ether->dest_mac[2],
this->ether->dest_mac[3], this->ether->dest_mac[4], this->ether->dest_mac[5]);
if (this->arp) {
ret += string_printf(",ARP,hw_type=%04hX,proto_type=%04hX,hw_addr_len=%02hhX,proto_addr_len=%02hhX,op=%04hX",
this->arp->hardware_type.load(), this->arp->protocol_type.load(), this->arp->hwaddr_len, this->arp->paddr_len, this->arp->operation.load());
} else if (this->ipv4) {
ret += string_printf(",IPv4,size=%04hX,src=%08" PRIX32 ",dest=%08" PRIX32,
this->ipv4->size.load(), this->ipv4->src_addr.load(), this->ipv4->dest_addr.load());
if (this->udp) {
ret += string_printf(",UDP,src_port=%04hX,dest_port=%04hX,size=%04hX",
this->udp->src_port.load(), this->udp->dest_port.load(), this->udp->size.load());
} else if (this->tcp) {
ret += string_printf(",TCP,src_port=%04hX,dest_port=%04hX,seq=%08" PRIX32 ",ack=%08" PRIX32 ",flags=%04hX(",
this->tcp->src_port.load(), this->tcp->dest_port.load(), this->tcp->seq_num.load(), this->tcp->ack_num.load(), this->tcp->flags.load());
if (this->tcp->flags & TCPHeader::Flag::FIN) {
ret += "FIN,";
}
if (this->tcp->flags & TCPHeader::Flag::SYN) {
ret += "SYN,";
}
if (this->tcp->flags & TCPHeader::Flag::RST) {
ret += "RST,";
}
if (this->tcp->flags & TCPHeader::Flag::PSH) {
ret += "PSH,";
}
if (this->tcp->flags & TCPHeader::Flag::ACK) {
ret += "ACK";
}
ret += ')';
} else {
ret += string_printf(",proto=%02hhX", this->ipv4->protocol);
}
} else {
ret += string_printf(",proto=%04hX", this->ether->protocol.load());
}
return ret;
}
void FrameInfo::truncate(size_t new_total_size) {
if (new_total_size > this->total_size) {
throw logic_error("truncate call expands frame size");
}
if (new_total_size < this->payload_size) {
throw logic_error("truncate call destroys part of header");
}
size_t delta_bytes = this->total_size - new_total_size;
this->total_size -= delta_bytes;
this->payload_size -= delta_bytes;
}
size_t FrameInfo::size_from_header() const {
if (this->ipv4) {
return this->ipv4->size;
} else if (this->arp) {
return sizeof(ARPHeader) + 2 * (this->arp->hwaddr_len + this->arp->paddr_len);
} else {
return 0;
}
}
uint16_t FrameInfo::computed_ipv4_header_checksum(const IPv4Header& ipv4) {
return ~collapse_checksum(
((ipv4.version_ihl << 8) | ipv4.tos) +
ipv4.size +
ipv4.id +
ipv4.frag_offset +
((ipv4.ttl << 8) | ipv4.protocol) +
(ipv4.src_addr >> 16) +
(ipv4.src_addr & 0xFFFF) +
(ipv4.dest_addr >> 16) +
(ipv4.dest_addr & 0xFFFF));
}
uint16_t FrameInfo::computed_ipv4_header_checksum() const {
if (!this->ipv4) {
throw logic_error("cannot compute ipv4 header checksum for non-ipv4 frame");
}
return this->computed_ipv4_header_checksum(*this->ipv4);
}
uint16_t FrameInfo::computed_udp4_checksum(
const IPv4Header& ipv4, const UDPHeader& udp, const void* data, size_t size) {
uint32_t sum =
(ipv4.src_addr >> 16) +
(ipv4.src_addr & 0xFFFF) +
(ipv4.dest_addr >> 16) +
(ipv4.dest_addr & 0xFFFF) +
ipv4.protocol +
udp.size +
udp.src_port +
udp.dest_port +
udp.size;
const uint8_t* u8_data = reinterpret_cast<const uint8_t*>(data);
for (size_t offset = 0; offset + 2 <= size; offset += 2) {
sum += *reinterpret_cast<const be_uint16_t*>(u8_data + offset);
}
if (size & 1) {
sum += u8_data[size - 1] << 8;
}
return ~collapse_checksum(sum);
}
uint16_t FrameInfo::computed_udp4_checksum() const {
if (!this->ipv4) {
throw logic_error("cannot compute udp header checksum for non-ipv4 frame");
}
if (!this->udp) {
throw logic_error("cannot compute udp header checksum for non-udp frame");
}
return this->computed_udp4_checksum(
*this->ipv4, *this->udp, this->payload, this->payload_size);
}
uint16_t FrameInfo::computed_tcp4_checksum(
const IPv4Header& ipv4, const TCPHeader& tcp, const void* data, size_t size) {
uint16_t tcp_size = ipv4.size - sizeof(IPv4Header);
uint32_t sum =
(ipv4.src_addr >> 16) +
(ipv4.src_addr & 0xFFFF) +
(ipv4.dest_addr >> 16) +
(ipv4.dest_addr & 0xFFFF) +
ipv4.protocol +
tcp_size +
tcp.src_port +
tcp.dest_port +
(tcp.seq_num >> 16) +
(tcp.seq_num & 0xFFFF) +
(tcp.ack_num >> 16) +
(tcp.ack_num & 0xFFFF) +
tcp.flags +
tcp.window +
tcp.urgent_ptr;
const uint8_t* u8_data = reinterpret_cast<const uint8_t*>(data);
for (size_t offset = 0; offset + 2 <= size; offset += 2) {
sum += *reinterpret_cast<const be_uint16_t*>(u8_data + offset);
}
if (size & 1) {
sum += u8_data[size - 1] << 8;
}
return ~collapse_checksum(sum);
}
uint16_t FrameInfo::computed_tcp4_checksum() const {
if (!this->ipv4) {
throw logic_error("cannot compute tcp header checksum for non-ipv4 frame");
}
if (!this->tcp) {
throw logic_error("cannot compute tcp header checksum for non-tcp frame");
}
return this->computed_tcp4_checksum(
*this->ipv4, *this->tcp, this->tcp + 1,
this->payload_size + this->tcp_options_size);
}
+105
View File
@@ -0,0 +1,105 @@
#pragma once
#include <stdint.h>
#include <phosg/Encoding.hh>
struct EthernetHeader {
uint8_t dest_mac[6];
uint8_t src_mac[6];
be_uint16_t protocol;
} __attribute__((packed));
struct ARPHeader {
be_uint16_t hardware_type;
be_uint16_t protocol_type; // same as EthernetHeader::protocol
uint8_t hwaddr_len;
uint8_t paddr_len;
be_uint16_t operation;
} __attribute__((packed));
struct IPv4Header {
uint8_t version_ihl;
uint8_t tos;
be_uint16_t size;
be_uint16_t id;
be_uint16_t frag_offset;
uint8_t ttl;
uint8_t protocol;
be_uint16_t checksum;
be_uint32_t src_addr;
be_uint32_t dest_addr;
} __attribute__((packed));
struct UDPHeader {
be_uint16_t src_port;
be_uint16_t dest_port;
be_uint16_t size;
be_uint16_t checksum;
} __attribute__((packed));
struct TCPHeader {
enum Flag {
NS = 0x0100,
CWR = 0x0080, // congestion window reduced
ECE = 0x0040, // ECN capable / congestion experienced
URG = 0x0020, // urgent pointer used
ACK = 0x0010, // ack_num is valid
PSH = 0x0008, // sending data
RST = 0x0004, // reset (hard disconnect)
SYN = 0x0002, // synchronize sequence numbers (open connection)
FIN = 0x0001, // close (normal disconnect)
};
be_uint16_t src_port;
be_uint16_t dest_port;
be_uint32_t seq_num;
be_uint32_t ack_num;
be_uint16_t flags;
be_uint16_t window;
be_uint16_t checksum;
be_uint16_t urgent_ptr;
} __attribute__((packed));
struct FrameInfo {
// This is always valid
const EthernetHeader* ether;
uint16_t ether_protocol;
// At most one of these is not null
const IPv4Header* ipv4;
const ARPHeader* arp;
// One of these may be not null if this->ipv4 is not null
const UDPHeader* udp;
const TCPHeader* tcp;
const void* header_start;
const void* payload;
size_t total_size;
size_t tcp_options_size;
size_t payload_size;
FrameInfo();
FrameInfo(const std::string& data);
FrameInfo(const void* data, size_t size);
std::string header_str() const;
void truncate(size_t new_total_size);
size_t size_from_header() const;
static uint16_t computed_ipv4_header_checksum(const IPv4Header& ipv4);
uint16_t computed_ipv4_header_checksum() const;
static uint16_t computed_udp4_checksum(
const IPv4Header& ipv4, const UDPHeader& udp, const void* data, size_t size);
uint16_t computed_udp4_checksum() const;
static uint16_t computed_tcp4_checksum(
const IPv4Header& ip, const TCPHeader& tcp, const void* data, size_t size);
uint16_t computed_tcp4_checksum() const;
};
+933
View File
@@ -0,0 +1,933 @@
#include "IPStackSimulator.hh"
#include <stdint.h>
#include <netinet/in.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#ifndef PHOSG_WINDOWS
#include <arpa/inet.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <string>
#include <phosg/Network.hh>
#include <phosg/Random.hh>
#include <phosg/Time.hh>
#include <event-async/Base.hh>
#include <event-async/Buffer.hh>
#include "IPFrameInfo.hh"
#include "DNSServer.hh"
using namespace std;
static const size_t DEFAULT_RESEND_PUSH_USECS = 200000; // 200ms
// Note: these functions exist because seq nums are allowed to wrap around the
// 32-bit integer space by design. We have to do the subtraction before the
// comparison to allow integer overflow to occur if needed.
static inline bool seq_num_less(uint32_t a, uint32_t b) {
return (a - b) & 0x80000000;
}
static inline bool seq_num_less_or_equal(uint32_t a, uint32_t b) {
return (a == b) || seq_num_less(a, b);
}
static inline bool seq_num_greater(uint32_t a, uint32_t b) {
return (b - a) & 0x80000000;
}
static __attribute__((unused)) inline bool seq_num_greater_or_equal(uint32_t a, uint32_t b) {
return (a == b) || seq_num_greater(a, b);
}
string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
be_uint32_t be_addr = addr;
char addr_str[INET_ADDRSTRLEN];
if (!inet_ntop(AF_INET, &be_addr, addr_str, INET_ADDRSTRLEN)) {
return string_printf("<UNKNOWN>:%hu", port);
} else {
return string_printf("%s:%hu", addr_str, port);
}
}
string IPStackSimulator::str_for_tcp_connection(shared_ptr<const IPClient> c,
const IPClient::TCPConnection& conn) {
uint64_t key = IPStackSimulator::tcp_conn_key_for_connection(conn);
string server_netloc_str = str_for_ipv4_netloc(conn.server_addr, conn.server_port);
string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn.client_port);
int fd = bufferevent_getfd(c->bev.get());
return string_printf("%d+%016" PRIX64 " (%s -> %s)",
fd, key, client_netloc_str.c_str(), server_netloc_str.c_str());
}
IPStackSimulator::IPStackSimulator(
std::shared_ptr<struct event_base> base,
std::shared_ptr<Server> game_server,
std::shared_ptr<ProxyServer> proxy_server,
std::shared_ptr<ServerState> state)
: base(base),
game_server(game_server),
proxy_server(proxy_server),
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);
}
IPStackSimulator::~IPStackSimulator() {
if (this->pcap_text_log_file) {
fclose(this->pcap_text_log_file);
}
}
void IPStackSimulator::listen(const std::string& socket_path) {
this->add_socket(::listen(socket_path, 0, SOMAXCONN));
}
void IPStackSimulator::listen(const std::string& addr, int port) {
this->add_socket(::listen(addr, port, SOMAXCONN));
}
void IPStackSimulator::listen(int port) {
this->add_socket(::listen("", port, SOMAXCONN));
}
void IPStackSimulator::add_socket(int fd) {
this->listeners.emplace(
evconnlistener_new(
this->base.get(),
IPStackSimulator::dispatch_on_listen_accept,
this,
LEV_OPT_REUSEABLE,
0,
fd),
evconnlistener_free);
}
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
// Episode III will think they're talking to a remote network and won't reject
// the connection.
if ((remote_addr & 0xFF000000) != 0x23000000) {
return 0x23232323;
} else {
return 0x24242424;
}
}
IPStackSimulator::IPClient::IPClient(struct bufferevent* bev)
: bev(bev, bufferevent_free), ipv4_addr(0) {
memset(this->mac_addr, 0, 6);
}
static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
IPStackSimulator::IPClient::TCPConnection::TCPConnection()
: server_bev(nullptr, flush_and_free_bufferevent),
pending_data(evbuffer_new(), evbuffer_free),
resend_push_event(nullptr, event_free) { }
void IPStackSimulator::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr *address, int socklen, void* ctx) {
reinterpret_cast<IPStackSimulator*>(ctx)->on_listen_accept(
listener, fd, address, socklen);
}
void IPStackSimulator::on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr*, int) {
int listen_fd = evconnlistener_get_fd(listener);
log(INFO, "[IPStackSimulator] Client fd %d connected via fd %d",
fd, listen_fd);
struct bufferevent *bev = bufferevent_socket_new(this->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
shared_ptr<IPClient> c(new IPClient(bev));
c->sim = this;
this->bev_to_client.emplace(make_pair(bev, c));
bufferevent_setcb(bev, &IPStackSimulator::dispatch_on_client_input, NULL,
&IPStackSimulator::dispatch_on_client_error, this);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
void IPStackSimulator::dispatch_on_listen_error(
struct evconnlistener* listener, void* ctx) {
reinterpret_cast<IPStackSimulator*>(ctx)->on_listen_error(listener);
}
void IPStackSimulator::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
log(ERROR, "[IPStackSimulator] Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), NULL);
}
void IPStackSimulator::dispatch_on_client_input(
struct bufferevent* bev, void* ctx) {
reinterpret_cast<IPStackSimulator*>(ctx)->on_client_input(bev);
}
void IPStackSimulator::on_client_input(struct bufferevent* bev) {
struct evbuffer* buf = bufferevent_get_input(bev);
shared_ptr<IPClient> c;
try {
c = this->bev_to_client.at(bev);
} catch (const out_of_range&) {
size_t bytes = evbuffer_get_length(buf);
log(ERROR, "[IPStackSimulator] Ignoring data received from unregistered client (0x%zX bytes)",
bytes);
evbuffer_drain(buf, bytes);
return;
}
while (evbuffer_get_length(buf) >= 2) {
uint16_t frame_size;
evbuffer_copyout(buf, &frame_size, 2);
if (evbuffer_get_length(buf) < frame_size + 2) {
break; // No complete frame available; done for now
}
evbuffer_drain(buf, 2);
string frame(frame_size, '\0');
evbuffer_remove(buf, frame.data(), frame.size());
try {
this->on_client_frame(c, frame);
} catch (const exception& e) {
log(WARNING, "[IPStackSimulator] Failed to process client frame: %s", e.what());
print_data(stderr, frame);
}
}
}
void IPStackSimulator::dispatch_on_client_error(
struct bufferevent* bev, short events, void* ctx) {
reinterpret_cast<IPStackSimulator*>(ctx)->on_client_error(bev, events);
}
void IPStackSimulator::on_client_error(struct bufferevent* bev,
short events) {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
log(WARNING, "[IPStackSimulator] Client caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
log(INFO, "[IPStackSimulator] Client fd %d disconnected",
bufferevent_getfd(bev));
this->bev_to_client.erase(bev);
}
}
void IPStackSimulator::on_client_frame(
shared_ptr<IPClient> c, const string& frame) {
if (this->state->ip_stack_debug) {
fputc('\n', stderr);
log(INFO, "[IPStackSimulator] Client sent frame");
print_data(stderr, frame);
}
this->log_frame(frame);
FrameInfo fi(frame);
if (this->state->ip_stack_debug) {
string fi_header = fi.header_str();
log(INFO, "[IPStackSimulator] Frame header: %s", fi_header.c_str());
}
if (fi.arp) {
this->on_client_arp_frame(c, fi);
} else if (fi.ipv4) {
uint16_t expected_ipv4_checksum = fi.computed_ipv4_header_checksum();
if (fi.ipv4->checksum != expected_ipv4_checksum) {
throw runtime_error(string_printf(
"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)) {
throw runtime_error("client sent IPv4 packet from different MAC address");
}
if (fi.ipv4->src_addr != c->ipv4_addr) {
throw runtime_error("client sent IPv4 packet from different IPv4 address");
}
if (fi.udp) {
uint16_t expected_udp_checksum = fi.computed_udp4_checksum();
if (fi.udp->checksum != expected_udp_checksum) {
throw runtime_error(string_printf(
"UDP checksum is incorrect (%04hX expected, %04hX received)",
expected_udp_checksum, fi.udp->checksum.load()));
}
this->on_client_udp_frame(c, fi);
} else if (fi.tcp) {
uint16_t expected_tcp_checksum = fi.computed_tcp4_checksum();
if (fi.tcp->checksum != expected_tcp_checksum) {
throw runtime_error(string_printf(
"TCP checksum is incorrect (%04hX expected, %04hX received)",
expected_tcp_checksum, fi.tcp->checksum.load()));
}
this->on_client_tcp_frame(c, fi);
} else {
throw runtime_error("frame uses unsupported IPv4 protocol");
}
} else {
throw runtime_error("frame is not IPv4");
}
}
void IPStackSimulator::on_client_arp_frame(
shared_ptr<IPClient> c, const FrameInfo& fi) {
if (fi.arp->hwaddr_len != 6 ||
fi.arp->paddr_len != 4 ||
fi.arp->hardware_type != 0x0001 ||
fi.arp->protocol_type != 0x0800) {
throw runtime_error("unsupported ARP parameters");
}
if (fi.payload_size < 20) {
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<const be_uint32_t*>(
reinterpret_cast<const uint8_t*>(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.protocol = fi.ether->protocol;
ARPHeader r_arp;
r_arp.hardware_type = fi.arp->hardware_type;
r_arp.protocol_type = fi.arp->protocol_type;
r_arp.hwaddr_len = 6;
r_arp.paddr_len = 4;
r_arp.operation = 0x0002;
// The incoming payload is:
// uint8_t src_mac[6]; // MAC address of client
// uint8_t src_ip[4]; // IP address of client
// uint8_t dest_mac[6]; // MAC address of host (all zeroes)
// uint8_t dest_ip[4]; // IP address of host
// The outgoing payload is:
// uint8_t dest_mac[6]; // MAC address of host (from configuration)
// uint8_t dest_ip[4]; // IP address of host
// uint8_t src_mac[6]; // MAC address of client
// uint8_t src_ip[4]; // IP address of client
const char* payload_bytes = reinterpret_cast<const char*>(fi.payload);
uint8_t r_payload[20];
memcpy(&r_payload[0], this->host_mac_address_bytes, 6);
memcpy(&r_payload[6], payload_bytes + 16, 4);
memcpy(&r_payload[10], payload_bytes, 10);
struct evbuffer* out_buf = bufferevent_get_output(c->bev.get());
uint16_t frame_size = sizeof(r_ether) + sizeof(r_arp) + sizeof(r_payload);
evbuffer_add(out_buf, &frame_size, 2);
evbuffer_add(out_buf, &r_ether, sizeof(r_ether));
evbuffer_add(out_buf, &r_arp, sizeof(r_arp));
evbuffer_add(out_buf, r_payload, sizeof(r_payload));
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] Sending ARP response");
}
if (this->pcap_text_log_file) {
StringWriter w;
w.write(&r_ether, sizeof(r_ether));
w.write(&r_arp, sizeof(r_arp));
w.write(r_payload, sizeof(r_payload));
this->log_frame(w.str());
}
}
void IPStackSimulator::on_client_udp_frame(
shared_ptr<IPClient> 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");
}
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.protocol = fi.ether->protocol;
IPv4Header r_ipv4;
r_ipv4.version_ihl = 0x45;
r_ipv4.tos = 0;
// r_ipv4.size filled in later
r_ipv4.id = 0;
r_ipv4.frag_offset = 0;
r_ipv4.ttl = 20; // TODO: Does this value actually matter? Looks like it just has to be nonzero
r_ipv4.protocol = 17; // UDP
// r_ipv4.checksum filled in later
r_ipv4.src_addr = fi.ipv4->dest_addr;
r_ipv4.dest_addr = fi.ipv4->src_addr;
UDPHeader r_udp;
r_udp.src_port = fi.udp->dest_port;
r_udp.dest_port = fi.udp->src_port;
// 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 = DNSServer::response_for_query(
fi.payload, fi.payload_size, resolved_address);
r_ipv4.size = sizeof(IPv4Header) + sizeof(UDPHeader) + r_data.size();
r_udp.size = sizeof(UDPHeader) + r_data.size();
r_ipv4.checksum = FrameInfo::computed_ipv4_header_checksum(r_ipv4);
r_udp.checksum = FrameInfo::computed_udp4_checksum(
r_ipv4, r_udp, r_data.data(), r_data.size());
struct evbuffer* out_buf = bufferevent_get_output(c->bev.get());
if (this->state->ip_stack_debug) {
string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
log(INFO, "[IPStackSimulator] Sending DNS response to %s", remote_str.c_str());
}
uint16_t frame_size = sizeof(r_ether) + sizeof(r_ipv4) + sizeof(r_udp) + r_data.size();
evbuffer_add(out_buf, &frame_size, 2);
evbuffer_add(out_buf, &r_ether, sizeof(r_ether));
evbuffer_add(out_buf, &r_ipv4, sizeof(r_ipv4));
evbuffer_add(out_buf, &r_udp, sizeof(r_udp));
evbuffer_add(out_buf, r_data.data(), r_data.size());
if (this->pcap_text_log_file) {
StringWriter w;
w.write(&r_ether, sizeof(r_ether));
w.write(&r_ipv4, sizeof(r_ipv4));
w.write(&r_udp, sizeof(r_udp));
w.write(r_data.data(), r_data.size());
this->log_frame(w.str());
}
}
uint64_t IPStackSimulator::tcp_conn_key_for_connection(
const IPClient::TCPConnection& conn) {
return (static_cast<uint64_t>(conn.server_addr) << 32) |
(static_cast<uint64_t>(conn.server_port) << 16) |
static_cast<uint64_t>(conn.client_port);
}
uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(
const IPv4Header& ipv4, const TCPHeader& tcp) {
return (static_cast<uint64_t>(ipv4.dest_addr) << 32) |
(static_cast<uint64_t>(tcp.dest_port) << 16) |
static_cast<uint64_t>(tcp.src_port);
}
uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const FrameInfo& fi) {
if (!fi.ipv4 || !fi.tcp) {
throw logic_error("tcp_conn_key_for_frame called on non-TCP frame");
}
return IPStackSimulator::tcp_conn_key_for_client_frame(*fi.ipv4, *fi.tcp);
}
void IPStackSimulator::on_client_tcp_frame(
shared_ptr<IPClient> c, const FrameInfo& fi) {
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] Client 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)) {
throw runtime_error("unsupported flag in TCP packet");
}
if (fi.tcp->flags & TCPHeader::Flag::SYN) {
// We never make connections back to the client, so we should never receive
// a SYN+ACK. Essentially, no other flags should be set in any received SYN.
if ((fi.tcp->flags & 0x0FFF) != TCPHeader::Flag::SYN) {
throw runtime_error("TCP SYN contains extra flags");
}
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
if (c->tcp_connections.count(key)) {
throw runtime_error("TCP SYN received for already-open connection");
}
StringReader options_r(fi.tcp + 1, fi.tcp_options_size);
size_t max_frame_size = 1400;
while (!options_r.eof()) {
uint8_t option = options_r.get_u8();
uint8_t option_size = (option < 2) ? 1 : options_r.get_u8();
switch (option) {
case 0: // End of options list
options_r.go(options_r.size());
break;
case 1: // No option (padding)
break;
case 2: // Max segment size
if (option_size != 4) {
throw runtime_error("incorrect size for TCP max frame size option");
}
max_frame_size = options_r.get_u16r();
break;
case 3: // Window scale (ignored)
if (option_size != 3) {
throw runtime_error("incorrect size for TCP window scale option");
}
options_r.skip(option_size);
break;
case 4: // Selective ACK supported (ignored)
if (option_size != 2) {
throw runtime_error("incorrect size for TCP selective ACK supported option");
}
break;
case 5: // Selective ACK (ignored)
options_r.skip(option_size - 2);
break;
case 8: // Timestamps (ignored)
if (option_size != 10) {
throw runtime_error("incorrect size for TCP timestamps option");
}
options_r.skip(8);
break;
default:
throw runtime_error("invalid TCP option");
}
}
// Set up the IPStackSimulator end of the virtual connection
auto& conn = c->tcp_connections.emplace(key, IPClient::TCPConnection()).first->second;
conn.client = c;
conn.resend_push_event.reset(event_new(this->base.get(), -1, EV_TIMEOUT,
&IPStackSimulator::dispatch_on_resend_push, &conn));
conn.server_addr = fi.ipv4->dest_addr;
conn.server_port = fi.tcp->dest_port;
conn.client_port = fi.tcp->src_port;
conn.next_client_seq = fi.tcp->seq_num + 1;
conn.acked_server_seq = random_object<uint32_t>();
conn.resend_push_usecs = DEFAULT_RESEND_PUSH_USECS;
conn.awaiting_first_ack = true;
conn.max_frame_size = max_frame_size;
string conn_str = this->state->ip_stack_debug ? this->str_for_tcp_connection(c, conn) : "";
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] Client opened TCP connection %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")",
conn_str.c_str(), conn.acked_server_seq, conn.next_client_seq);
}
// Send a SYN+ACK (send_tcp_frame always adds the ACK flag)
this->send_tcp_frame(c, conn, TCPHeader::Flag::SYN);
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] 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 {
// This frame isn't a SYN, so a connection object should already exist
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
IPClient::TCPConnection* conn;
try {
conn = &c->tcp_connections.at(key);
} catch (const out_of_range&) {
throw runtime_error("non-SYN frame does not correspond to any open TCP connection");
}
bool conn_valid = true;
if (fi.tcp->flags & TCPHeader::Flag::ACK) {
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] 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");
}
conn->acked_server_seq++;
conn->awaiting_first_ack = false;
} else {
if (seq_num_greater(fi.tcp->ack_num, conn->acked_server_seq)) {
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] 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) {
throw runtime_error("client acknowledged beyond end of sent data");
}
evbuffer_drain(conn->pending_data.get(), ack_delta);
conn->acked_server_seq += ack_delta;
conn->resend_push_usecs = DEFAULT_RESEND_PUSH_USECS;
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] 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)) {
throw runtime_error("client sent lower ack num than previous frame");
}
}
if (!conn->server_bev.get()) {
this->open_server_connection(c, *conn);
}
}
if (fi.tcp->flags & (TCPHeader::Flag::RST | TCPHeader::Flag::FIN)) {
bool is_rst = (fi.tcp->flags & TCPHeader::Flag::RST);
if (is_rst && (fi.tcp->flags & TCPHeader::Flag::FIN)) {
throw runtime_error("client sent TCP FIN+RST");
}
if (this->state->ip_stack_debug) {
string conn_str = this->str_for_tcp_connection(c, *conn);
log(INFO, "[IPStackSimulator] Client closed TCP connection %s", conn_str.c_str());
}
// TODO: Are we supposed to send a response to an RST? Here we do, and the
// client probably just ignores it anyway
this->send_tcp_frame(c, *conn, fi.tcp->flags & (TCPHeader::Flag::RST | TCPHeader::Flag::FIN));
// Delete the connection object. The unique_ptr destructor flushes the
// bufferevent, and thereby sends an EOF to the server's end.
c->tcp_connections.erase(key);
conn_valid = false;
// Note: The PSH flag isn't required to be set on all packets that contain
// data. The PSH flag just means "tell the application that data is
// available", so some senders only set the PSH flag on the last frame of a
// large segment of data, since the application wouldn't be able to process
// the segment until all of it is available. newserv can handle incomplete
// commands, so we just ignore the PSH flag and forward any data to the
// server immediately.
} else if (fi.payload_size != 0) {
string conn_str = this->state->ip_stack_debug ? this->str_for_tcp_connection(c, *conn) : "";
size_t payload_skip_bytes;
if (fi.tcp->seq_num == conn->next_client_seq) {
payload_skip_bytes = 0;
} else if (seq_num_less(fi.tcp->seq_num, conn->next_client_seq)) {
// If the frame overlaps an existing boundary, we'll accept some of the
// data; otherwise we'll ignore it entirely (but still send an ACK)
uint32_t end_seq = fi.tcp->seq_num + fi.payload_size;
if (seq_num_less_or_equal(end_seq, conn->next_client_seq)) { // Fully "in the past"
payload_skip_bytes = fi.payload_size;
} else { // Partially "in the past"
payload_skip_bytes = fi.payload_size - (end_seq - conn->next_client_seq);
}
} else {
// Payload is in the future - we must have missed a data frame. We'll
// ignore it (but warn) and send an ACK later, and the client should
// retransmit the lost data
if (this->state->ip_stack_debug) {
log(WARNING,
"[IPStackSimulator] Client sent out-of-order sequence number (expected %08" PRIX32 ", received %08" PRIX32 ", 0x%zX data bytes)",
conn->next_client_seq, fi.tcp->seq_num.load(), fi.payload_size);
}
payload_skip_bytes = fi.payload_size;
}
if (payload_skip_bytes > fi.payload_size) {
throw logic_error("payload skip bytes too large");
}
if (payload_skip_bytes < fi.payload_size) {
const void* payload = reinterpret_cast<const uint8_t*>(fi.payload) + payload_skip_bytes;
size_t payload_size = fi.payload_size - payload_skip_bytes;
if (this->state->ip_stack_debug) {
if (payload_skip_bytes) {
log(INFO, "[IPStackSimulator] Client sent data on TCP connection %s, overlapping existing ack'ed data (0x%zX bytes ignored)",
conn_str.c_str(), payload_skip_bytes);
} else {
log(INFO, "[IPStackSimulator] Client sent data on TCP connection %s",
conn_str.c_str());
}
print_data(stderr, payload, payload_size);
}
// Send the new data to the server
struct evbuffer* server_out_buf = bufferevent_get_output(
conn->server_bev.get());
evbuffer_add(server_out_buf, payload, payload_size);
// Update the sequence number and stats
conn->next_client_seq += payload_size;
conn->bytes_received += payload_size;
}
// Send an ACK
this->send_tcp_frame(c, *conn);
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] 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);
}
}
if (conn_valid) {
// Try to send some more data if the client is waiting on it
this->send_pending_push_frame(c, *conn);
}
}
}
void IPStackSimulator::open_server_connection(
shared_ptr<IPClient> c, IPClient::TCPConnection& conn) {
if (conn.server_bev.get()) {
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);
// Set up the IPStackSimulator end of the virtual connection
bufferevent_setcb(bevs[0], &IPStackSimulator::dispatch_on_server_input, NULL,
&IPStackSimulator::dispatch_on_server_error, &conn);
bufferevent_enable(bevs[0], EV_READ | EV_WRITE);
conn.server_bev.reset(bevs[0]);
// 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
if (this->game_server.get()) {
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]);
}
string conn_str = this->str_for_tcp_connection(c, conn);
log(INFO, "[IPStackSimulator] Connected TCP connection %s to game server",
conn_str.c_str());
}
void IPStackSimulator::send_pending_push_frame(
shared_ptr<IPClient> c, IPClient::TCPConnection& conn) {
size_t pending_bytes = evbuffer_get_length(conn.pending_data.get());
if (!pending_bytes) {
return;
}
size_t bytes_to_send = min<size_t>(pending_bytes, conn.max_frame_size);
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] 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(),
bytes_to_send);
struct timeval resend_push_timeout = usecs_to_timeval(conn.resend_push_usecs);
event_add(conn.resend_push_event.get(), &resend_push_timeout);
// If the client isn't responding to our PSHes, back off exponentially up to
// a limit of 5 seconds between PSH frames. This window is reset when
// acked_server_seq changes (that is, when the client has acknowledged any new
// data)
conn.resend_push_usecs *= 2;
if (conn.resend_push_usecs > 5000000) {
conn.resend_push_usecs = 5000000;
}
}
void IPStackSimulator::send_tcp_frame(
shared_ptr<IPClient> c,
IPClient::TCPConnection& conn,
uint16_t flags,
struct evbuffer* src_buf,
size_t src_bytes) {
if (!src_bytes != !(flags & TCPHeader::Flag::PSH)) {
throw logic_error("data should be given if and only if PSH is given");
}
EthernetHeader ether;
memcpy(ether.dest_mac, c->mac_addr, 6);
memcpy(ether.src_mac, this->host_mac_address_bytes, 6);
ether.protocol = 0x0800; // IPv4
IPv4Header ipv4;
ipv4.version_ihl = 0x45;
ipv4.tos = 0;
// ipv4.size filled in later
ipv4.id = 0;
ipv4.frag_offset = 0;
ipv4.ttl = 20;
ipv4.protocol = 6; // TCP
// ipv4.checksum filled in later
ipv4.src_addr = conn.server_addr;
ipv4.dest_addr = c->ipv4_addr;
TCPHeader tcp;
tcp.src_port = conn.server_port;
tcp.dest_port = conn.client_port;
tcp.seq_num = conn.acked_server_seq;
tcp.ack_num = conn.next_client_seq;
tcp.flags = (5 << 12) | TCPHeader::Flag::ACK | flags;
tcp.window = 0x1000;
tcp.urgent_ptr = 0;
// tcp.checksum filled in later
ipv4.size = sizeof(IPv4Header) + sizeof(TCPHeader) + src_bytes;
ipv4.checksum = FrameInfo::computed_ipv4_header_checksum(ipv4);
const void* linear_data = src_bytes ? evbuffer_pullup(src_buf, src_bytes) : nullptr;
tcp.checksum = FrameInfo::computed_tcp4_checksum(
ipv4, tcp, linear_data, src_bytes);
struct evbuffer* out_buf = bufferevent_get_output(c->bev.get());
uint16_t frame_size = sizeof(ether) + sizeof(ipv4) + sizeof(tcp) + src_bytes;
evbuffer_add(out_buf, &frame_size, 2);
evbuffer_add(out_buf, &ether, sizeof(ether));
evbuffer_add(out_buf, &ipv4, sizeof(ipv4));
evbuffer_add(out_buf, &tcp, sizeof(tcp));
if (src_bytes) {
evbuffer_add(out_buf, linear_data, src_bytes);
}
if (this->pcap_text_log_file) {
StringWriter w;
w.write(&ether, sizeof(ether));
w.write(&ipv4, sizeof(ipv4));
w.write(&tcp, sizeof(tcp));
w.write(linear_data, src_bytes);
this->log_frame(w.str());
}
}
void IPStackSimulator::dispatch_on_resend_push(evutil_socket_t, short, void* ctx) {
auto* conn = reinterpret_cast<IPClient::TCPConnection*>(ctx);
auto c = conn->client.lock();
if (!c.get()) {
log(WARNING, "[IPStackSimulator] Resend push event triggered for deleted client; ignoring");
} else {
c->sim->on_resend_push(c, *conn);
}
}
void IPStackSimulator::on_resend_push(shared_ptr<IPClient> c, IPClient::TCPConnection& conn) {
this->send_pending_push_frame(c, conn);
}
void IPStackSimulator::dispatch_on_server_input(struct bufferevent*, void* ctx) {
auto* conn = reinterpret_cast<IPClient::TCPConnection*>(ctx);
auto c = conn->client.lock();
if (!c.get()) {
log(WARNING, "[IPStackSimulator] Server input event triggered for deleted client; ignoring");
} else {
c->sim->on_server_input(c, *conn);
}
}
void IPStackSimulator::on_server_input(shared_ptr<IPClient> c, IPClient::TCPConnection& conn) {
struct evbuffer* buf = bufferevent_get_input(conn.server_bev.get());
if (this->state->ip_stack_debug) {
log(INFO, "[IPStackSimulator] Server input event: 0x%zX bytes to read",
evbuffer_get_length(buf));
}
evbuffer_add_buffer(conn.pending_data.get(), buf);
this->send_pending_push_frame(c, conn);
}
void IPStackSimulator::dispatch_on_server_error(
struct bufferevent*, short events, void* ctx) {
auto* conn = reinterpret_cast<IPClient::TCPConnection*>(ctx);
auto c = conn->client.lock();
if (!c.get()) {
log(WARNING, "[IPStackSimulator] Server error event triggered for deleted client; ignoring");
} else {
c->sim->on_server_error(c, *conn, events);
}
}
void IPStackSimulator::on_server_error(
shared_ptr<IPClient> c, IPClient::TCPConnection& conn, short events) {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
log(WARNING, "[IPStackSimulator] Received error %d from virtual connection (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
// Send an RST to the client. Kind of rude (we really should use FIN) but
// the PSO network stack always sends an RST to us when disconnecting, so
// whatever
this->send_tcp_frame(c, conn, TCPHeader::Flag::RST);
// Delete the connection object (this also flushes and frees the server
// virtual connection bufferevent)
c->tcp_connections.erase(this->tcp_conn_key_for_connection(conn));
}
}
void IPStackSimulator::log_frame(const string& data) const {
if (this->pcap_text_log_file) {
print_data(this->pcap_text_log_file, data, 0, nullptr,
PrintDataFlags::SkipSeparator);
fputc('\n', this->pcap_text_log_file);
fflush(this->pcap_text_log_file);
}
}
+144
View File
@@ -0,0 +1,144 @@
#include <stdint.h>
#include <netinet/in.h>
#include <deque>
#include <string>
#include <phosg/Process.hh>
#include <phosg/Filesystem.hh>
#include <event-async/Task.hh>
#include <event-async/Base.hh>
#include "IPFrameInfo.hh"
#include "Server.hh"
#include "ProxyServer.hh"
#include "ServerState.hh"
class IPStackSimulator {
public:
IPStackSimulator(
std::shared_ptr<struct event_base> base,
std::shared_ptr<Server> game_server,
std::shared_ptr<ProxyServer> proxy_server,
std::shared_ptr<ServerState> state);
~IPStackSimulator();
void listen(const std::string& socket_path);
void listen(const std::string& addr, int port);
void listen(int port);
void add_socket(int fd);
static uint32_t connect_address_for_remote_address(uint32_t remote_addr);
private:
std::shared_ptr<struct event_base> base;
std::shared_ptr<Server> game_server;
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<ServerState> state;
using unique_listener = std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)>;
using unique_bufferevent = std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>;
using unique_evbuffer = std::unique_ptr<struct evbuffer, void(*)(struct evbuffer*)>;
using unique_event = std::unique_ptr<struct event, void(*)(struct event*)>;
struct IPClient {
IPStackSimulator* sim;
unique_bufferevent bev;
uint8_t mac_addr[6];
uint32_t ipv4_addr;
struct TCPConnection {
std::weak_ptr<IPClient> client;
// The PSO protocol begins with the server sending a command, but we
// shouldn't send a PSH immediately after the SYN+ACK, so the connection
// isn't handed to the Server object until after the 3-way handshake
// (receive SYN, send SYN+ACK, receive ACK). This means server_bev is null
// during the first part of the connection phase.
unique_bufferevent server_bev;
// TODO: Get rid of pending_data and just use server_bev's input buffer in
// its place
unique_evbuffer pending_data;
unique_event resend_push_event;
bool awaiting_first_ack;
uint32_t server_addr;
uint16_t server_port;
uint16_t client_port;
uint32_t next_client_seq;
uint32_t acked_server_seq;
size_t resend_push_usecs;
size_t max_frame_size;
size_t bytes_received;
size_t bytes_sent;
TCPConnection();
};
std::unordered_map<uint64_t, TCPConnection> tcp_connections;
IPClient(struct bufferevent* bev);
};
std::unordered_set<unique_listener> listeners;
std::unordered_map<struct bufferevent*, std::shared_ptr<IPClient>> bev_to_client;
uint8_t host_mac_address_bytes[6];
uint8_t broadcast_mac_address_bytes[6];
FILE* pcap_text_log_file;
static uint64_t tcp_conn_key_for_connection(
const IPClient::TCPConnection& conn);
static uint64_t tcp_conn_key_for_client_frame(
const IPv4Header& ipv4, const TCPHeader& tcp);
static uint64_t tcp_conn_key_for_client_frame(const FrameInfo& fi);
static std::string str_for_ipv4_netloc(uint32_t addr, uint16_t port);
static std::string str_for_tcp_connection(std::shared_ptr<const IPClient> c,
const IPClient::TCPConnection& conn);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
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);
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 on_client_error(struct bufferevent* bev, short events);
void on_client_frame(std::shared_ptr<IPClient> c, const std::string& frame);
void on_client_arp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
void on_client_udp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
void on_client_tcp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
static void dispatch_on_resend_push(evutil_socket_t fd, short events,
void* ctx);
void on_resend_push(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn);
static void dispatch_on_server_input(struct bufferevent* bev, void* ctx);
void on_server_input(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn);
static void dispatch_on_server_error(struct bufferevent* bev, short events,
void* ctx);
void on_server_error(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn, short events);
void send_pending_push_frame(
std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn);
void send_tcp_frame(
std::shared_ptr<IPClient> c,
IPClient::TCPConnection& conn,
uint16_t flags = 0,
struct evbuffer* src_buf = nullptr,
size_t src_bytes = 0);
void open_server_connection(
std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn);
void log_frame(const std::string& data) const;
};
+24 -3
View File
@@ -20,6 +20,7 @@
#include "Text.hh"
#include "ServerShell.hh"
#include "ProxyShell.hh"
#include "IPStackSimulator.hh"
using namespace std;
@@ -83,7 +84,7 @@ void populate_state_from_config(shared_ptr<ServerState> s,
} catch (const out_of_range&) { }
// TODO: make this configurable
s->port_configuration = default_port_to_behavior;
s->set_port_configuration(default_port_to_behavior);
auto enemy_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Enemy"));
auto box_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Box"));
@@ -140,11 +141,20 @@ void populate_state_from_config(shared_ptr<ServerState> s,
s->all_addresses.emplace("<external>", s->external_address);
try {
s->dns_server_port = d.at("RunDNSServer")->as_bool();
s->dns_server_port = d.at("DNSServerPort")->as_int();
} catch (const out_of_range&) {
s->dns_server_port = 0;
}
try {
for (const auto& item : d.at("IPStackListen")->as_list()) {
s->ip_stack_addresses.emplace_back(item->as_string());
}
} catch (const out_of_range&) { }
try {
s->ip_stack_debug = d.at("IPStackDebug")->as_bool();
} catch (const out_of_range&) { }
try {
s->allow_unregistered_users = d.at("AllowUnregisteredUsers")->as_bool();
} catch (const out_of_range&) {
@@ -265,7 +275,7 @@ int main(int argc, char* argv[]) {
} else {
log(INFO, "Starting game server");
game_server.reset(new Server(base, state));
for (const auto& it : state->port_configuration) {
for (const auto& it : state->named_port_configuration) {
game_server->listen("", it.second.port, it.second.version, it.second.behavior);
}
@@ -282,6 +292,17 @@ int main(int argc, char* argv[]) {
state->quest_index.reset(new QuestIndex("system/quests"));
}
shared_ptr<IPStackSimulator> ip_stack_simulator;
if (!state->ip_stack_addresses.empty()) {
log(INFO, "Starting IP stack simulator");
ip_stack_simulator.reset(new IPStackSimulator(
base, game_server, proxy_server, state));
for (const auto& it : state->ip_stack_addresses) {
auto netloc = parse_netloc(it);
ip_stack_simulator->listen(netloc.first, netloc.second);
}
}
if (!state->username.empty()) {
log(INFO, "Switching to user %s", state->username.c_str());
drop_privileges(state->username);
+56 -31
View File
@@ -27,10 +27,17 @@ using namespace std;
static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
ProxyServer::ProxyServer(shared_ptr<struct event_base> base,
const struct sockaddr_storage& initial_destination, GameVersion version) :
base(base), client_bev(nullptr, bufferevent_free),
server_bev(nullptr, bufferevent_free),
base(base), client_bev(nullptr, flush_and_free_bufferevent),
server_bev(nullptr, flush_and_free_bufferevent),
next_destination(initial_destination), version(version),
header_size((version == GameVersion::BB) ? 8 : 4) {
memset(&this->client_input_header, 0, sizeof(this->client_input_header));
@@ -110,24 +117,37 @@ void ProxyServer::dispatch_on_server_error(struct bufferevent* bev, short events
void ProxyServer::on_listen_accept(struct evconnlistener*, evutil_socket_t fd,
struct sockaddr*, int) {
if (this->client_bev.get()) {
log(WARNING, "Ignoring client connection because client already exists");
log(WARNING, "[ProxyServer] Ignoring client connection because client already exists");
close(fd);
return;
} else {
log(INFO, "Client connected");
}
this->client_bev.reset(bufferevent_socket_new(this->base.get(), fd,
log(INFO, "[ProxyServer] Client connected on fd %d", fd);
this->on_client_connect(bufferevent_socket_new(this->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
}
void ProxyServer::connect_client(struct bufferevent* bev) {
if (this->client_bev.get()) {
log(WARNING, "[ProxyServer] Ignoring client virtual connection because client already exists");
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
return;
}
log(INFO, "[ProxyServer] Client connected on virtual connection %p", bev);
this->on_client_connect(bev);
}
void ProxyServer::on_client_connect(struct bufferevent* bev) {
this->client_bev.reset(bev);
bufferevent_setcb(this->client_bev.get(),
&ProxyServer::dispatch_on_client_input, NULL,
&ProxyServer::dispatch_on_client_error, this);
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
// connect to the server, disconnecting first if needed
// Connect to the server, disconnecting first if needed
this->server_bev.reset(bufferevent_socket_new(this->base.get(), -1,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
@@ -144,7 +164,7 @@ void ProxyServer::on_listen_accept(struct evconnlistener*, evutil_socket_t fd,
sin.sin_addr.s_addr = sin_ss->sin_addr.s_addr;
string netloc_str = render_sockaddr_storage(this->next_destination);
log(INFO, "connecting to %s", netloc_str.c_str());
log(INFO, "[ProxyServer] Connecting to %s", netloc_str.c_str());
if (bufferevent_socket_connect(this->server_bev.get(),
reinterpret_cast<const sockaddr*>(&sin), sizeof(sin)) != 0) {
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
@@ -157,7 +177,7 @@ void ProxyServer::on_listen_accept(struct evconnlistener*, evutil_socket_t fd,
void ProxyServer::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
log(ERROR, "failure on listening socket %d: %d (%s)",
log(ERROR, "[ProxyServer] Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), NULL);
}
@@ -173,11 +193,11 @@ void ProxyServer::on_server_input(struct bufferevent*) {
void ProxyServer::on_client_error(struct bufferevent*, short events) {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
log(WARNING, "error %d (%s) in client stream", err,
log(WARNING, "[ProxyServer] Error %d (%s) in client stream", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
log(INFO, "client has disconnected");
log(INFO, "[ProxyServer] Client has disconnected");
this->client_bev.reset();
// "forward" the disconnection to the server
this->server_bev.reset();
@@ -193,11 +213,11 @@ void ProxyServer::on_client_error(struct bufferevent*, short events) {
void ProxyServer::on_server_error(struct bufferevent*, short events) {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
log(WARNING, "error %d (%s) in server stream", err,
log(WARNING, "[ProxyServer] Error %d (%s) in server stream", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
log(INFO, "server has disconnected");
log(INFO, "[ProxyServer] Server has disconnected");
this->server_bev.reset();
// "forward" the disconnection to the client
this->client_bev.reset();
@@ -259,7 +279,7 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
break;
}
//log(INFO, "[ProxyServer-debug] received encrypted header");
//log(INFO, "[ProxyServer-debug] Received encrypted header");
//print_data(stderr, input_header, this->header_size);
if (source_crypt) {
@@ -269,7 +289,7 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
size_t command_size = this->get_size_field(input_header);
if (evbuffer_get_length(source_buf) < command_size) {
//log(INFO, "[ProxyServer-debug] insufficient data for command (%zX/%hX bytes)", evbuffer_get_length(source_buf), this->get_size_field(input_header));
//log(INFO, "[ProxyServer-debug] Insufficient data for command (%zX/%hX bytes)", evbuffer_get_length(source_buf), this->get_size_field(input_header));
break;
}
@@ -278,11 +298,11 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
if (bytes < static_cast<ssize_t>(command_size)) {
throw logic_error("enough bytes available, but could not remove them");
}
//log(INFO, "[ProxyServer-debug] read command (%zX bytes)", bytes);
//log(INFO, "[ProxyServer-debug] Read command (%zX bytes)", bytes);
// overwrite the header with the already-decrypted header
memcpy(command.data(), input_header, this->header_size);
//log(INFO, "[ProxyServer-debug] received encrypted command with pre-decrypted header");
//log(INFO, "[ProxyServer-debug] Received encrypted command with pre-decrypted header");
//print_data(stderr, command);
if (source_crypt) {
@@ -290,7 +310,7 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
command_size - this->header_size);
}
log(INFO, "%s:", from_server ? "server" : "client");
log(INFO, "[ProxyServer] %s:", from_server ? "server" : "client");
print_data(stderr, command);
// preprocess the command if needed
@@ -353,20 +373,25 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
sin->sin_addr.s_addr = args->address; // already network byte order
if (!dest_bev) {
log(WARNING, "received reconnect command with no destination present");
log(WARNING, "[ProxyServer] Received reconnect command with no destination present");
} else {
struct sockaddr_storage sockname_ss;
socklen_t len = sizeof(sockname_ss);
getsockname(bufferevent_getfd(dest_bev),
reinterpret_cast<struct sockaddr*>(&sockname_ss), &len);
if (sockname_ss.ss_family != AF_INET) {
throw logic_error("existing connection is not ipv4");
}
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 {
getsockname(fd, reinterpret_cast<struct sockaddr*>(&sockname_ss), &len);
if (sockname_ss.ss_family != AF_INET) {
throw logic_error("existing connection is not ipv4");
}
struct sockaddr_in* sockname_sin = reinterpret_cast<struct sockaddr_in*>(
&sockname_ss);
args->address = sockname_sin->sin_addr.s_addr; // already network byte order
args->port = this->listeners.begin()->first;
struct sockaddr_in* sockname_sin = reinterpret_cast<struct sockaddr_in*>(
&sockname_ss);
args->address = sockname_sin->sin_addr.s_addr; // Already network byte order
args->port = ntohs(sockname_sin->sin_port); // Client expects this little-endian for some reason
}
}
break;
}
@@ -378,12 +403,12 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
if (dest_crypt) {
dest_crypt->encrypt(command.data(), command.size());
}
//log(INFO, "[ProxyServer-debug] sending encrypted command");
//log(INFO, "[ProxyServer-debug] Sending encrypted command");
//print_data(stderr, command);
evbuffer_add(dest_buf, command.data(), command.size());
} else {
log(WARNING, "no destination present; dropping command");
log(WARNING, "[ProxyServer] No destination present; dropping command");
}
// clear the input header so we can read the next command
+4
View File
@@ -24,6 +24,8 @@ public:
void listen(int port);
void connect_client(struct bufferevent* bev);
void send_to_client(const std::string& data);
void send_to_server(const std::string& data);
@@ -64,6 +66,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);
size_t get_size_field(const PSOCommandHeader* header);
size_t get_command_field(const PSOCommandHeader* header);
+6 -6
View File
@@ -43,8 +43,8 @@ enum ClientStateBB {
void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c) {
switch (c->server_behavior) {
case ServerBehavior::SplitReconnect: {
uint16_t pc_port = s->port_configuration.at("pc-login").port;
uint16_t gc_port = s->port_configuration.at("gc-jp10").port;
uint16_t pc_port = s->named_port_configuration.at("pc-login").port;
uint16_t gc_port = s->named_port_configuration.at("gc-jp10").port;
send_pc_gc_split_reconnect(c, s->connect_address_for_client(c), pc_port, gc_port);
c->should_disconnect = true;
break;
@@ -373,7 +373,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
case ClientStateBB::InitialLogin:
// first login? send them to the other port
send_reconnect(c, s->connect_address_for_client(c),
s->port_configuration.at("bb-data1").port);
s->named_port_configuration.at("bb-data1").port);
break;
case ClientStateBB::DownloadData: {
@@ -399,7 +399,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
default:
send_reconnect(c, s->connect_address_for_client(c),
s->port_configuration.at("bb-login").port);
s->named_port_configuration.at("bb-login").port);
}
}
@@ -626,7 +626,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
send_reconnect(c, s->connect_address_for_client(c),
s->port_configuration.at(port_name).port);
s->named_port_configuration.at(port_name).port);
break;
}
@@ -842,7 +842,7 @@ void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
send_reconnect(c, s->connect_address_for_client(c),
s->port_configuration.at(port_name).port);
s->named_port_configuration.at(port_name).port);
}
void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c,
+35 -3
View File
@@ -36,10 +36,18 @@ void Server::disconnect_client(shared_ptr<Client> c) {
struct bufferevent* bev = c->bev;
c->bev = NULL;
int fd = bufferevent_getfd(bev);
if (fd < 0) {
log(INFO, "[Server] Client on virtual connection %p disconnected", bev);
} else {
log(INFO, "[Server] Client on fd %d disconnected", fd);
}
// if the output buffer is not empty, move the client into the draining pool
// instead of disconnecting it, to make sure all the data gets sent
struct evbuffer* out_buffer = bufferevent_get_output(bev);
if (evbuffer_get_length(out_buffer) == 0) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
} else {
// the callbacks will free it when all the data is sent or the client
@@ -98,7 +106,7 @@ void Server::on_listen_accept(struct evconnlistener* listener,
return;
}
log(INFO, "[Server] Client connected via fd %d", listen_fd);
log(INFO, "[Server] Client fd %d connected via fd %d", fd, listen_fd);
struct bufferevent *bev = bufferevent_socket_new(this->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
@@ -113,6 +121,28 @@ void Server::on_listen_accept(struct evconnlistener* listener,
process_connect(this->state, c);
}
void Server::connect_client(
struct bufferevent* bev, uint32_t address, uint16_t port,
GameVersion version, ServerBehavior initial_state) {
log(INFO, "[Server] Client connected on virtual connection %p", bev);
shared_ptr<Client> c(new Client(bev, version, initial_state));
this->bev_to_client.emplace(make_pair(bev, c));
// Manually set the remote address, since the bufferevent has no fd and the
// Client constructor can't figure out the virtual remote address
auto* sin = reinterpret_cast<sockaddr_in*>(&c->remote_addr);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = htonl(address);
sin->sin_port = htons(port);
bufferevent_setcb(bev, &Server::dispatch_on_client_input, NULL,
&Server::dispatch_on_client_error, this);
bufferevent_enable(bev, EV_READ | EV_WRITE);
process_connect(this->state, c);
}
void Server::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
log(ERROR, "[Server] Failure on listening socket %d: %d (%s)",
@@ -149,6 +179,7 @@ void Server::on_client_input(struct bufferevent* bev) {
}
void Server::on_disconnecting_client_output(struct bufferevent* bev) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
@@ -171,6 +202,7 @@ void Server::on_disconnecting_client_error(struct bufferevent* bev,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
}
@@ -233,7 +265,7 @@ Server::Server(shared_ptr<struct event_base> base,
void Server::listen(const string& socket_path, GameVersion version,
ServerBehavior behavior) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
log(INFO, "[Server] Listening on unix socket %s (version %s) on fd %d",
log(INFO, "[Server] Listening on Unix socket %s (%s) on fd %d",
socket_path.c_str(), name_for_version(version), fd);
this->add_socket(fd, version, behavior);
}
@@ -242,7 +274,7 @@ void Server::listen(const string& addr, int port, GameVersion version,
ServerBehavior behavior) {
int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port);
log(INFO, "[Server] Listening on tcp interface %s (version %s) on fd %d",
log(INFO, "[Server] Listening on TCP interface %s (%s) on fd %d",
netloc_str.c_str(), name_for_version(version), fd);
this->add_socket(fd, version, behavior);
}
+3
View File
@@ -26,6 +26,9 @@ public:
void listen(int port, GameVersion version, ServerBehavior initial_state);
void add_socket(int fd, GameVersion version, ServerBehavior initial_state);
void connect_client(struct bufferevent* bev, uint32_t address, uint16_t port,
GameVersion version, ServerBehavior initial_state);
private:
std::shared_ptr<struct event_base> base;
+29 -5
View File
@@ -6,6 +6,7 @@
#include "SendCommands.hh"
#include "NetworkAddresses.hh"
#include "IPStackSimulator.hh"
#include "Text.hh"
using namespace std;
@@ -14,6 +15,7 @@ using namespace std;
ServerState::ServerState()
: dns_server_port(0),
ip_stack_debug(false),
allow_unregistered_users(false),
run_shell_behavior(RunShellBehavior::Default), next_lobby_id(1),
pre_lobby_event(0) {
@@ -157,11 +159,33 @@ shared_ptr<Client> ServerState::find_client(const char16_t* identifier,
}
uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
// TODO: we can do something much smarter here, like use the sockname to find
// out which interface the client is connected to, and return that address
if (is_local_address(c->remote_addr)) {
return this->local_address;
if (c->is_virtual_connection) {
if (c->remote_addr.ss_family != AF_INET) {
throw logic_error("virtual connection is missing remote IPv4 address");
}
const auto* sin = reinterpret_cast<const sockaddr_in*>(&c->remote_addr);
return IPStackSimulator::connect_address_for_remote_address(
ntohl(sin->sin_addr.s_addr));
} else {
return this->external_address;
// TODO: we can do something smarter here, like use the sockname to find
// out which interface the client is connected to, and return that address
if (is_local_address(c->remote_addr)) {
return this->local_address;
} else {
return this->external_address;
}
}
}
void ServerState::set_port_configuration(
const std::unordered_map<std::string, PortConfiguration>& named_port_configuration) {
this->named_port_configuration = named_port_configuration;
this->numbered_port_configuration.clear();
for (const auto& it : this->named_port_configuration) {
if (!this->numbered_port_configuration.emplace(it.second.port, it.second).second) {
throw runtime_error("duplicate port in configuration");
}
}
}
+7 -1
View File
@@ -32,9 +32,12 @@ struct ServerState {
};
std::u16string name;
std::unordered_map<std::string, PortConfiguration> port_configuration;
std::unordered_map<std::string, PortConfiguration> named_port_configuration;
std::unordered_map<uint16_t, PortConfiguration> numbered_port_configuration;
std::string username;
uint16_t dns_server_port;
std::vector<std::string> ip_stack_addresses;
bool ip_stack_debug;
bool allow_unregistered_users;
RunShellBehavior run_shell_behavior;
PSOBBEncryption::KeyFile default_key_file;
@@ -78,4 +81,7 @@ struct ServerState {
uint64_t serial_number = 0, std::shared_ptr<Lobby> l = NULL);
uint32_t connect_address_for_client(std::shared_ptr<Client> c);
void set_port_configuration(
const std::unordered_map<std::string, PortConfiguration>& named_port_configuration);
};
+18
View File
@@ -3,13 +3,31 @@
// Server's name (maximum 16 characters)
"ServerName": "Alexandria",
// Address to connect local clients to (IP address or interface name)
"LocalAddress": "192.168.0.5",
// Address to connect external clients to (IP address or interface name)
"ExternalAddress": "en0",
// Port to listen for DNS queries on. Set this to zero (or comment it out) to
// disable the DNS server.
"DNSServerPort": 53,
// Where to listen for IP stack clients. This exists to interface with PSO GC
// clients running in a local Dolphin emulator. To enable local Dolphin
// clients to connect, set this to ["/tmp/dolphin-tap"] and configure Dolphin
// to use the tapserver type of broadband adapter. You do not need to install
// or run tapserver. See README.md for details on how to get PSO to connect
// via this interface.
// If you're doing inadvisable things, you can also add numbers or
// "address:port" strings to this list to listen for tapserver connections on
// a TCP port.
// "IPStackListen": ["/tmp/dolphin-tap"],
// Set this to true to show a lot of debugging information from the IP stack
// simulator.
// "IPStackDebug": true,
// By default, the interactive shell runs if stdin is a terminal, and doesn't
// run if it's not. This option, if present, overrides that behavior.
// "RunInteractiveShell": false,