add IP stack simulator
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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, ðer, 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(ðer, 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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user