diff --git a/src/IPFrameInfo.cc b/src/IPFrameInfo.cc index c0e241ec..ca5156ca 100644 --- a/src/IPFrameInfo.cc +++ b/src/IPFrameInfo.cc @@ -13,120 +13,130 @@ static inline uint16_t collapse_checksum(uint32_t sum) { 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(LinkType link_type, const string& data) + : FrameInfo(link_type, data.data(), data.size()) {} -FrameInfo::FrameInfo(const string& data) : FrameInfo(data.data(), data.size()) {} +FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size) + : FrameInfo() { + this->link_type = link_type; + this->header_start = header_start; + this->total_size = size; + this->payload_size = 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) { + StringReader r(header_start, 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(header_start); - this->ether_protocol = this->ether->protocol; + // Parse link-layer header + Protocol proto = Protocol::NONE; + switch (this->link_type) { + case LinkType::ETHERNET: + this->payload_size -= sizeof(EthernetHeader); + this->ether = &r.get(); + this->ether_protocol = this->ether->protocol; + // Unwrap VLAN tags if necessary + while ((this->ether_protocol == 0x8100) || (this->ether_protocol == 0x88A8)) { + r.skip(2); + this->ether_protocol = r.get_u16b(); + this->payload_size -= 4; + } + switch (this->ether_protocol) { + case 0x0800: + proto = Protocol::IPV4; + break; + case 0x0806: + proto = Protocol::ARP; + break; + } + break; - // Figure out the protocol - const be_uint16_t* u16data = reinterpret_cast(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; + case LinkType::HDLC: + this->payload_size -= (sizeof(HDLCHeader) + 3); // Trim off checksum and end sentinel + this->hdlc = &r.get(); + this->hdlc_checksum = r.pget_u16b(r.where() + this->payload_size); + switch (this->hdlc->protocol) { + case 0xC021: + proto = Protocol::LCP; + break; + case 0xC023: + proto = Protocol::PAP; + break; + case 0x8021: + proto = Protocol::IPCP; + break; + case 0x0021: + proto = Protocol::IPV4; + break; + } + break; + + default: + throw logic_error("invalid link type"); } - // 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(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"); + // Parse inner protocol headers + switch (proto) { + case Protocol::NONE: + throw runtime_error("unknown protocol"); + case Protocol::LCP: + this->payload_size -= sizeof(LCPHeader); + this->lcp = &r.get(); + break; + case Protocol::PAP: + this->payload_size -= sizeof(PAPHeader); + this->pap = &r.get(); + break; + case Protocol::IPCP: + this->payload_size -= sizeof(IPCPHeader); + this->ipcp = &r.get(); + break; + case Protocol::IPV4: + this->ipv4 = &r.get(); + if (this->payload_size < this->ipv4->size) { + throw invalid_argument("ipv4 header specifies size larger than frame"); } - this->tcp = reinterpret_cast(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->payload_size = this->ipv4->size - sizeof(IPv4Header); + + if (this->ipv4->protocol == 0x06) { + this->tcp = &r.get(); + 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; + r.skip(tcp_header_size - sizeof(TCPHeader)); + + } else if (this->ipv4->protocol == 0x11) { + this->payload_size -= sizeof(UDPHeader); + this->udp = &r.get(); } - this->tcp_options_size = tcp_header_size - sizeof(TCPHeader); - this->payload_size -= tcp_header_size; - this->payload = reinterpret_cast(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(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(u16data); - this->payload = this->arp + 1; - - } else { - throw runtime_error("unknown protocol"); + break; + case Protocol::ARP: + this->payload_size -= sizeof(ARPHeader); + this->arp = &r.get(); + break; } + + this->payload = r.getv(this->payload_size); } string FrameInfo::header_str() const { - if (!this->ether) { + if (!this->ether && !this->hdlc) { return ""; } - 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]); + string ret; + if (this->ether) { + ret = string_printf( + "ETHER:%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]); + } else if (this->hdlc) { + ret = string_printf("HDLC:%02hhX/%02hhX", this->hdlc->address, this->hdlc->control); + } else { + return ""; + } if (this->arp) { ret += string_printf( @@ -169,7 +179,11 @@ string FrameInfo::header_str() const { } } else { - ret += string_printf(",proto=%04hX", this->ether->protocol.load()); + if (this->ether) { + ret += string_printf(",proto=%04hX", this->ether->protocol.load()); + } else if (this->hdlc) { + ret += string_printf(",proto=%04hX", this->hdlc->protocol.load()); + } } return ret; @@ -292,3 +306,26 @@ uint16_t FrameInfo::computed_tcp4_checksum() const { *this->ipv4, *this->tcp, this->tcp + 1, this->payload_size + this->tcp_options_size); } + +uint16_t FrameInfo::computed_hdlc_checksum(const void* vdata, size_t size) { + const uint8_t* data = reinterpret_cast(vdata); + uint16_t crc = 0xFFFF; + for (size_t z = 0; z < size; z++) { + crc ^= data[z]; + for (size_t b = 0; b < 8; b++) { + crc = (crc & 1) ? ((crc >> 1) ^ 0x8408) : (crc >> 1); + } + } + return ~crc; +} + +uint16_t FrameInfo::computed_hdlc_checksum() const { + if (!this->hdlc) { + throw logic_error("cannot compute HDLC checksum for non-HDLC frame"); + } + return this->computed_hdlc_checksum(&this->hdlc->address, this->total_size - 4); +} + +uint16_t FrameInfo::stored_hdlc_checksum() const { + return *reinterpret_cast(reinterpret_cast(this->header_start) + (this->total_size - 3)); +} diff --git a/src/IPFrameInfo.hh b/src/IPFrameInfo.hh index 8ce47107..a3a2e92b 100644 --- a/src/IPFrameInfo.hh +++ b/src/IPFrameInfo.hh @@ -3,9 +3,35 @@ #include #include +#include #include "Text.hh" +struct HDLCHeader { + uint8_t start_sentinel1; // 0x7E + uint8_t address; // 0xFF usually + uint8_t control; // 0x03 for PPP + be_uint16_t protocol; +} __attribute__((packed)); + +struct LCPHeader { + uint8_t command; + uint8_t request_id; + be_uint16_t size; +} __attribute__((packed)); + +struct PAPHeader { + uint8_t command; + uint8_t request_id; + be_uint16_t size; +} __attribute__((packed)); + +struct IPCPHeader { + uint8_t command; + uint8_t request_id; + be_uint16_t size; +} __attribute__((packed)); + struct EthernetHeader { parray dest_mac; parray src_mac; @@ -82,40 +108,75 @@ struct DHCPHeader { } __attribute__((packed)); struct FrameInfo { - // This is always valid - const EthernetHeader* ether; - uint16_t ether_protocol; + enum class LinkType { + ETHERNET = 0, + HDLC, + }; + + enum class Protocol { + NONE = 0, + LCP, + PAP, + IPCP, + IPV4, + ARP, + // TODO: Some less-common protocols that we might want to support: + // Ether / HDLC = proto + // 0x8035 / ?????? = RARP + // 0x809B / 0x0029 = AppleTalk + // 0x80F3 / ?????? = AppleTalk ARP + // 0x8137 / 0x002B = IPX + }; + + LinkType link_type = LinkType::ETHERNET; + + // Exactly one of these headers is valid + const EthernetHeader* ether = nullptr; + uint16_t ether_protocol = 0; + const HDLCHeader* hdlc = nullptr; + uint16_t hdlc_checksum = 0; + + // One of these may be non-null if hdlc is valid + const LCPHeader* lcp = nullptr; + const PAPHeader* pap = nullptr; + const IPCPHeader* ipcp = nullptr; // At most one of these is not null - const IPv4Header* ipv4; - const ARPHeader* arp; + const IPv4Header* ipv4 = nullptr; + const ARPHeader* arp = nullptr; // One of these may be not null if this->ipv4 is not null - const UDPHeader* udp; - const TCPHeader* tcp; + const UDPHeader* udp = nullptr; + const TCPHeader* tcp = nullptr; - const void* header_start; - const void* payload; - size_t total_size; - size_t tcp_options_size; - size_t payload_size; + const void* header_start = nullptr; + const void* payload = nullptr; + size_t total_size = 0; + size_t tcp_options_size = 0; + size_t payload_size = 0; - FrameInfo(); - FrameInfo(const std::string& data); - FrameInfo(const void* data, size_t size); + FrameInfo() = default; + FrameInfo(LinkType link_type, const std::string& data); + FrameInfo(LinkType link_type, const void* data, size_t size); std::string header_str() const; + inline StringReader read_payload() const { + return StringReader(this->payload, this->payload_size); + } + 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); + 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); + static uint16_t computed_tcp4_checksum(const IPv4Header& ip, const TCPHeader& tcp, const void* data, size_t size); uint16_t computed_tcp4_checksum() const; + + static uint16_t computed_hdlc_checksum(const void* data, size_t size); + uint16_t computed_hdlc_checksum() const; + uint16_t stored_hdlc_checksum() const; }; diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index 24275c3a..614d938c 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -21,6 +21,65 @@ using namespace std; static const size_t DEFAULT_RESEND_PUSH_USECS = 200000; // 200ms +static string unescape_hdlc_frame(const void* data, size_t size) { + StringReader r(data, size); + if (r.get_u8(data) != 0x7E) { + throw runtime_error("HDLC frame does not begin with 7E"); + } + string ret("\x7E", 1); + + while (r.get_u8(false) != 0x7E) { + uint8_t ch = r.get_u8(); + if (ch == 0x7D) { + ch = r.get_u8(); + if (ch == 0x7E) { + throw runtime_error("abort sequence received"); + } + ret.push_back(ch ^ 0x20); + } else { + ret.push_back(ch); + } + } + ret.push_back(0x7E); + return ret; +} + +static string unescape_hdlc_frame(const string& data) { + return unescape_hdlc_frame(data.data(), data.size()); +} + +static string escape_hdlc_frame(const void* data, size_t size, uint32_t escape_control_character_flags = 0xFFFFFFFF) { + if (size < 2) { + throw runtime_error("HDLC frame too small for start and end sentinels"); + } + + StringReader r(data, size); + if (r.pget_u8(size - 1) != 0x7E) { + throw runtime_error("HDLC frame does not end with 7E"); + } + r.truncate(size - 1); + if (r.get_u8() != 0x7E) { + throw runtime_error("HDLC frame does not begin with 7E"); + } + string ret("\x7E", 1); + + while (!r.eof()) { + uint8_t ch = r.get_u8(); + if ((ch == 0x7D) || (ch == 0x7E) || ((ch < 0x20) && ((escape_control_character_flags >> ch) & 1))) { + ret.push_back(0x7D); + ret.push_back(ch ^ 0x20); + } else { + ret.push_back(ch); + } + } + ret.push_back(0x7E); + return ret; +} + +static string escape_hdlc_frame(const string& data, uint32_t escape_control_character_flags = 0xFFFFFFFF) { + return escape_hdlc_frame(data.data(), data.size(), escape_control_character_flags); +} + // 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. @@ -51,8 +110,7 @@ string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) { } } -string IPStackSimulator::str_for_tcp_connection(shared_ptr c, - const IPClient::TCPConnection& conn) { +string IPStackSimulator::str_for_tcp_connection(shared_ptr 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); @@ -77,28 +135,28 @@ IPStackSimulator::~IPStackSimulator() { } } -void IPStackSimulator::listen(const string& name, const string& socket_path) { +void IPStackSimulator::listen(const string& name, const string& socket_path, FrameInfo::LinkType link_type) { int fd = ::listen(socket_path, 0, SOMAXCONN); ip_stack_simulator_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, name.c_str()); - this->add_socket(name, fd); + this->add_socket(name, fd, link_type); } -void IPStackSimulator::listen(const string& name, const string& addr, int port) { +void IPStackSimulator::listen(const string& name, const string& addr, int port, FrameInfo::LinkType link_type) { if (port == 0) { - this->listen(name, addr); + this->listen(name, addr, link_type); } else { int fd = ::listen(addr, port, SOMAXCONN); string netloc_str = render_netloc(addr, port); ip_stack_simulator_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, name.c_str()); - this->add_socket(name, fd); + this->add_socket(name, fd, link_type); } } -void IPStackSimulator::listen(const string& name, int port) { - this->listen(name, "", port); +void IPStackSimulator::listen(const string& name, int port, FrameInfo::LinkType link_type) { + this->listen(name, "", port, link_type); } -void IPStackSimulator::add_socket(const string& name, int fd) { +void IPStackSimulator::add_socket(const string& name, int fd, FrameInfo::LinkType link_type) { unique_listener l( evconnlistener_new( this->base.get(), @@ -108,7 +166,7 @@ void IPStackSimulator::add_socket(const string& name, int fd) { 0, fd), evconnlistener_free); - this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(name, std::move(l))); + this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(name, link_type, std::move(l))); } uint32_t IPStackSimulator::connect_address_for_remote_address(uint32_t remote_addr) { @@ -122,9 +180,10 @@ uint32_t IPStackSimulator::connect_address_for_remote_address(uint32_t remote_ad } } -IPStackSimulator::IPClient::IPClient(shared_ptr sim, struct bufferevent* bev) +IPStackSimulator::IPClient::IPClient(shared_ptr sim, FrameInfo::LinkType link_type, struct bufferevent* bev) : sim(sim), bev(bev, bufferevent_free), + link_type(link_type), mac_addr(0), ipv4_addr(0), idle_timeout_event(event_new(sim->base.get(), -1, EV_TIMEOUT, &IPStackSimulator::IPClient::dispatch_on_idle_timeout, this), event_free) { @@ -196,7 +255,7 @@ void IPStackSimulator::on_listen_accept(struct evconnlistener* listener, struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); - auto c = make_shared(this->shared_from_this(), bev); + auto c = make_shared(this->shared_from_this(), listening_socket->link_type, bev); this->bev_to_client.emplace(make_pair(bev, c)); bufferevent_setcb(bev, &IPStackSimulator::dispatch_on_client_input, nullptr, @@ -274,21 +333,143 @@ void IPStackSimulator::on_client_error(struct bufferevent* bev, short events) { } } -void IPStackSimulator::on_client_frame( - shared_ptr c, const string& frame) { - if (ip_stack_simulator_log.debug("Virtual network sent frame")) { - print_data(stderr, frame); - fputc('\n', stderr); - } - this->log_frame(frame); +void IPStackSimulator::send_layer3_frame(shared_ptr c, FrameInfo::Protocol proto, const string& data) const { + this->send_layer3_frame(c, proto, data.data(), data.size()); +} - FrameInfo fi(frame); +void IPStackSimulator::send_layer3_frame(shared_ptr c, FrameInfo::Protocol proto, const void* data, size_t size) const { + struct evbuffer* out_buf = bufferevent_get_output(c->bev.get()); + + switch (c->link_type) { + case FrameInfo::LinkType::ETHERNET: { + EthernetHeader ether; + ether.dest_mac = c->mac_addr; + ether.src_mac = this->host_mac_address_bytes; + switch (proto) { + case FrameInfo::Protocol::NONE: + throw logic_error("layer 3 protocol not specified"); + case FrameInfo::Protocol::LCP: + throw logic_error("cannot send LCP frame over Ethernet"); + case FrameInfo::Protocol::IPV4: + ether.protocol = 0x0800; + break; + case FrameInfo::Protocol::ARP: + ether.protocol = 0x0806; + break; + default: + throw logic_error("unknown layer 3 protocol"); + } + + le_uint16_t frame_size = size + sizeof(EthernetHeader); + evbuffer_add(out_buf, &frame_size, 2); + evbuffer_add(out_buf, ðer, sizeof(ether)); + evbuffer_add(out_buf, data, size); + if (this->pcap_text_log_file) { + StringWriter w; + w.write(ðer, sizeof(ether)); + w.write(data, size); + this->log_frame(w.str()); + } + break; + } + + case FrameInfo::LinkType::HDLC: { + HDLCHeader hdlc; + hdlc.start_sentinel1 = 0x7E; + hdlc.address = 0xFF; + hdlc.control = 0x03; + switch (proto) { + case FrameInfo::Protocol::NONE: + throw logic_error("layer 3 protocol not specified"); + case FrameInfo::Protocol::LCP: + hdlc.protocol = 0xC021; + break; + case FrameInfo::Protocol::PAP: + hdlc.protocol = 0xC023; + break; + case FrameInfo::Protocol::IPCP: + hdlc.protocol = 0x8021; + break; + case FrameInfo::Protocol::IPV4: + hdlc.protocol = 0x0021; + break; + case FrameInfo::Protocol::ARP: + throw runtime_error("cannot send ARP packets over HDLC"); + default: + throw logic_error("unknown layer 3 protocol"); + } + + StringWriter w; + w.put(hdlc); + w.write(data, size); + w.put_u16l(FrameInfo::computed_hdlc_checksum(w.str().data() + 1, w.size() - 1)); + w.put_u8(0x7E); + + string escaped = escape_hdlc_frame(w.str(), c->hdlc_escape_control_character_flags); + if (ip_stack_simulator_log.debug("Sending HDLC frame to virtual network (escaped to %zX bytes)", escaped.size())) { + print_data(stderr, w.str()); + } + + le_uint16_t frame_size = escaped.size(); + evbuffer_add(out_buf, &frame_size, 2); + evbuffer_add(out_buf, escaped.data(), escaped.size()); + if (this->pcap_text_log_file) { + this->log_frame(escaped); + } + break; + } + + default: + throw logic_error("unknown link type"); + } +} + +void IPStackSimulator::on_client_frame(shared_ptr c, const string& frame) { + const string* effective_data = &frame; + string hdlc_unescaped_data; + if (c->link_type == FrameInfo::LinkType::HDLC) { + hdlc_unescaped_data = unescape_hdlc_frame(frame); + effective_data = &hdlc_unescaped_data; + } + if (ip_stack_simulator_log.debug("Virtual network sent frame")) { + print_data(stderr, *effective_data); + } + this->log_frame(*effective_data); + + FrameInfo fi(c->link_type, *effective_data); if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) { string fi_header = fi.header_str(); ip_stack_simulator_log.debug("Frame header: %s", fi_header.c_str()); } - if (fi.arp) { + if (fi.ether) { + if (c->mac_addr.is_filled_with(0)) { + c->mac_addr = fi.ether->src_mac; + } else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) { + throw runtime_error("client sent IPv4 packet from different MAC address"); + } + } else if (fi.hdlc) { + uint16_t expected_checksum = fi.computed_hdlc_checksum(); + uint16_t stored_checksum = fi.stored_hdlc_checksum(); + if (expected_checksum != stored_checksum) { + throw runtime_error(string_printf( + "HDLC checksum is incorrect (%04hX expected, %04hX received)", + expected_checksum, stored_checksum)); + } + } else { + throw runtime_error("frame is not Ethernet or HDLC"); + } + + if (fi.lcp) { + this->on_client_lcp_frame(c, fi); + + } else if (fi.pap) { + this->on_client_pap_frame(c, fi); + + } else if (fi.ipcp) { + this->on_client_ipcp_frame(c, fi); + + } else if (fi.arp) { this->on_client_arp_frame(c, fi); } else if (fi.ipv4) { @@ -299,12 +480,6 @@ void IPStackSimulator::on_client_frame( expected_ipv4_checksum, fi.ipv4->checksum.load())); } - // Populate the client's addresses if needed - if (c->mac_addr.is_filled_with(0)) { - c->mac_addr = fi.ether->src_mac; - } else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) { - throw runtime_error("client sent IPv4 packet from different MAC address"); - } if ((fi.ipv4->src_addr != c->ipv4_addr) && (fi.ipv4->src_addr != 0)) { throw runtime_error("client sent IPv4 packet from different IPv4 address"); } @@ -336,6 +511,261 @@ void IPStackSimulator::on_client_frame( } } +void IPStackSimulator::on_client_lcp_frame(shared_ptr c, const FrameInfo& fi) { + switch (fi.lcp->command) { + case 0x01: { // Configure-Request + auto opts_r = fi.read_payload(); + while (!opts_r.eof()) { + uint8_t opt = opts_r.get_u8(); + string opt_data = opts_r.read(opts_r.get_u8() - 2); + StringReader opt_data_r(opt_data); + switch (opt) { + case 0x01: // Maximum receive unit + // TODO: Currently we ignore this, but we probably should use it. + opt_data_r.get_u16b(); + break; + case 0x02: // Escaped control character flags + c->hdlc_escape_control_character_flags = opt_data_r.get_u32b(); + break; + case 0x05: // Magic-Number + c->hdlc_remote_magic_number = opt_data_r.get_u32b(); + break; + case 0x00: // RESERVED + case 0x03: // Authentication protocol + case 0x04: // Quality protocol + case 0x07: // Protocol field compression + case 0x08: // Address and control field compression + throw runtime_error(string_printf("unimplemented LCP option %02hhX (%zu bytes)", opt, opt_data.size())); + default: + throw runtime_error("unknown LCP option"); + } + } + // Technically, we should implement the LCP state machine, but I'm too + // lazy to do this right now. In our situation, it should suffice to + // simply always send a Configure-Request to the client with a magic + // number not equal to the one we received. + StringWriter opts_w; + opts_w.put_u8(0x01); // Maximum receive unit + opts_w.put_u8(0x04); + opts_w.put_u16b(1500); + opts_w.put_u8(0x02); // Escaped control character flags (we don't require any) + opts_w.put_u8(0x06); + opts_w.put_u32(0); + opts_w.put_u8(0x03); // Authentication protocol + opts_w.put_u8(0x04); + opts_w.put_u16b(0xC023); // Password authentication protocol + opts_w.put_u8(0x05); // Magic number (bitwise inverse of the remote end's) + opts_w.put_u8(0x06); + opts_w.put_u32b(~c->hdlc_remote_magic_number); + StringWriter request_w; + request_w.put(LCPHeader{ + .command = 0x01, // Configure-Request + .request_id = fi.lcp->request_id, + .size = static_cast(sizeof(LCPHeader) + opts_w.size()), + }); + request_w.write(opts_w.str()); + this->send_layer3_frame(c, FrameInfo::Protocol::LCP, request_w.str()); + + StringWriter ack_w; + ack_w.put(LCPHeader{ + .command = 0x02, // Configure-Ack + .request_id = fi.lcp->request_id, + .size = fi.lcp->size, + }); + ack_w.write(fi.payload, fi.payload_size); + this->send_layer3_frame(c, FrameInfo::Protocol::LCP, ack_w.str()); + + break; + } + + case 0x05: { // Terminate-Request + c->ipv4_addr = 0; + c->tcp_connections.clear(); + string response(reinterpret_cast(fi.payload), fi.payload_size); + response.at(0) = 0x06; // Terminate-Ack + this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response); + break; + } + + case 0x09: { // Echo-Request + string response(reinterpret_cast(fi.payload), fi.payload_size); + response.at(0) = 0x0A; // Echo-Reply + this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response); + break; + } + + case 0x0B: // Discard-Request + case 0x02: // Configure-Ack + break; + + case 0x03: // Configure-Nak + case 0x04: // Configure-Reject + case 0x06: // Terminate-Ack + case 0x07: // Code-Reject + case 0x08: // Protocol-Reject + case 0x0A: // Echo-Reply + throw runtime_error("unimplemented LCP command"); + default: + throw runtime_error("unknown LCP command"); + } +} + +void IPStackSimulator::on_client_pap_frame(shared_ptr c, const FrameInfo& fi) { + if (fi.pap->command != 0x01) { // Authenticate-Request + throw runtime_error("client sent incorrect PAP command"); + } + + auto r = fi.read_payload(); + string username = r.read(r.get_u8()); + string password = r.read(r.get_u8()); + ip_stack_simulator_log.info("Client logged in with username \"%s\" and password", username.c_str()); + + static const string login_message = "newserv PPP simulator"; + StringWriter w; + w.put(PAPHeader{ + .command = 0x02, // Authenticate-Ack + .request_id = fi.pap->request_id, + .size = login_message.size() + sizeof(PAPHeader) + 1, + }); + w.put_u8(login_message.size()); + w.write(login_message); + this->send_layer3_frame(c, FrameInfo::Protocol::PAP, w.str()); +} + +void IPStackSimulator::on_client_ipcp_frame(shared_ptr c, const FrameInfo& fi) { + switch (fi.ipcp->command) { + case 0x01: { // Configure-Request + auto opts_r = fi.read_payload(); + + uint32_t remote_ip = 0; + uint32_t remote_primary_dns = 0; + uint32_t remote_secondary_dns = 0; + StringWriter rejected_opts_w; + while (!opts_r.eof()) { + uint8_t opt = opts_r.get_u8(); + string opt_data = opts_r.read(opts_r.get_u8() - 2); + StringReader opt_data_r(opt_data); + switch (opt) { + case 0x01: // IP addresses (deprecated as of 1992; we don't support it at all) + throw runtime_error("IPCP client sent IP-Addresses option"); + case 0x02: // IP compression protocol + rejected_opts_w.put_u8(0x02); + rejected_opts_w.put_u8(opt_data_r.size() + 2); + rejected_opts_w.write(opt_data); + break; + case 0x03: // IP address + remote_ip = opt_data_r.get_u32b(); + break; + case 0x81: // Primary DNS server address + remote_primary_dns = opt_data_r.get_u32b(); + break; + case 0x83: // Secondary DNS server address + remote_secondary_dns = opt_data_r.get_u32b(); + break; + case 0x82: // Primary NBNS server address + case 0x84: // Secondary NBNS server address + case 0x04: // Mobile IP address + throw runtime_error(string_printf("unimplemented IPCP option %02hhX (%zu bytes)", opt, opt_data.size())); + default: + throw runtime_error("unknown IPCP option"); + } + } + + if (!rejected_opts_w.str().empty()) { + // Send a Configure-Reject if the client specified IP header compression + StringWriter reject_w; + reject_w.put(IPCPHeader{ + .command = 0x04, // Configure-Reject + .request_id = fi.ipcp->request_id, + .size = sizeof(IPCPHeader) + rejected_opts_w.size(), + }); + reject_w.write(rejected_opts_w.str()); + this->send_layer3_frame(c, FrameInfo::Protocol::IPCP, reject_w.str()); + + } else if ((remote_ip != 0x1E1E1E1E) || + (remote_primary_dns != 0x23232323) || + (remote_secondary_dns != 0x24242424)) { + // Send a Configure-Nak if the client's request doesn't exactly match + // what we want them to use. + StringWriter opts_w; + opts_w.put_u8(0x03); // IP address + opts_w.put_u8(0x06); + opts_w.put_u32b(0x1E1E1E1E); + opts_w.put_u8(0x81); // Primary DNS server address + opts_w.put_u8(0x06); + opts_w.put_u32b(0x23232323); + opts_w.put_u8(0x83); // Secondary DNS server address + opts_w.put_u8(0x06); + opts_w.put_u32b(0x24242424); + + StringWriter nak_w; + nak_w.put(IPCPHeader{ + .command = 0x03, // Configure-Nak + .request_id = fi.ipcp->request_id, + .size = static_cast(opts_w.size() + sizeof(IPCPHeader)), + }); + nak_w.write(opts_w.str()); + this->send_layer3_frame(c, FrameInfo::Protocol::IPCP, nak_w.str()); + + } else { // Options OK + c->ipv4_addr = remote_ip; + + // As with LCP, we technically should implement the state machine, but I + // continue to be lazy. + StringWriter opts_w; + opts_w.put_u8(0x03); // IP address + opts_w.put_u8(0x06); + opts_w.put_u32b(0x39393939); + opts_w.put_u8(0x81); // Primary DNS server address + opts_w.put_u8(0x06); + opts_w.put_u32b(0x23232323); + opts_w.put_u8(0x83); // Secondary DNS server address + opts_w.put_u8(0x06); + opts_w.put_u32b(0x24242424); + + StringWriter request_w; + request_w.put(IPCPHeader{ + .command = 0x01, // Configure-Request + .request_id = fi.ipcp->request_id, + .size = static_cast(opts_w.size() + sizeof(IPCPHeader)), + }); + request_w.write(opts_w.str()); + this->send_layer3_frame(c, FrameInfo::Protocol::IPCP, request_w.str()); + + StringWriter ack_w; + ack_w.put(IPCPHeader{ + .command = 0x02, // Configure-Ack + .request_id = fi.ipcp->request_id, + .size = fi.ipcp->size, + }); + ack_w.write(fi.payload, fi.payload_size); + this->send_layer3_frame(c, FrameInfo::Protocol::IPCP, ack_w.str()); + } + break; + } + + case 0x05: { // Terminate-Request + c->ipv4_addr = 0; + c->tcp_connections.clear(); + string response(reinterpret_cast(fi.payload), fi.payload_size); + response.at(0) = 0x06; // Terminate-Ack + this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response); + break; + } + + case 0x02: // Configure-Ack + break; + + case 0x03: // Configure-Nak + case 0x04: // Configure-Reject + case 0x06: // Terminate-Ack + case 0x07: // Code-Reject + throw runtime_error("unimplemented IPCP command"); + default: + throw runtime_error("unknown LCP command"); + } +} + void IPStackSimulator::on_client_arp_frame( shared_ptr c, const FrameInfo& fi) { if (fi.arp->hwaddr_len != 6 || @@ -353,17 +783,14 @@ void IPStackSimulator::on_client_arp_frame( reinterpret_cast(fi.payload) + 6); } - EthernetHeader r_ether; - r_ether.dest_mac = fi.ether->src_mac; - r_ether.src_mac = this->host_mac_address_bytes; - r_ether.protocol = fi.ether->protocol; - - ARPHeader r_arp; - 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; + StringWriter w; + w.put(ARPHeader{ + .hardware_type = fi.arp->hardware_type, + .protocol_type = fi.arp->protocol_type, + .hwaddr_len = 6, + .paddr_len = 4, + .operation = 0x0002, + }); // The incoming payload is: // uint8_t src_mac[6]; // MAC address of client @@ -375,43 +802,19 @@ void IPStackSimulator::on_client_arp_frame( // 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(fi.payload); + w.write(this->host_mac_address_bytes.data(), 6); + w.write(payload_bytes + 16, 4); + w.write(payload_bytes, 10); - uint8_t r_payload[20]; - memcpy(&r_payload[0], this->host_mac_address_bytes.data(), 6); - memcpy(&r_payload[6], payload_bytes + 16, 4); - memcpy(&r_payload[10], payload_bytes, 10); - - 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)); - - ip_stack_simulator_log.debug("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()); - } + this->send_layer3_frame(c, FrameInfo::Protocol::ARP, w.str()); } -void IPStackSimulator::on_client_udp_frame( - shared_ptr c, const FrameInfo& fi) { - // We only implement DHCP and newserv's DNS server here +void IPStackSimulator::on_client_udp_frame(shared_ptr c, const FrameInfo& fi) { + // We only implement DHCP and newserv's DNS server here. // Every received UDP packet will elicit exactly one UDP response from // newserv, so we prepare the response headers in advance - EthernetHeader r_ether; - r_ether.dest_mac = fi.ether->src_mac; - r_ether.src_mac = this->host_mac_address_bytes; - r_ether.protocol = fi.ether->protocol; IPv4Header r_ipv4; r_ipv4.version_ihl = 0x45; @@ -433,7 +836,7 @@ void IPStackSimulator::on_client_udp_frame( string r_data; if (fi.udp->dest_port == 67) { // DHCP - StringReader r(fi.payload, fi.payload_size); + auto r = fi.read_payload(); const auto& dhcp = r.get(); if (dhcp.hardware_type != 1) { throw runtime_error("unknown DHCP hardware type"); @@ -566,29 +969,18 @@ void IPStackSimulator::on_client_udp_frame( 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 (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) { string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port); ip_stack_simulator_log.debug("Sending UDP response to %s", remote_str.c_str()); print_data(stderr, r_data); } - 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()); + StringWriter w; + w.put(r_ipv4); + w.put(r_udp); + w.write(r_data); - 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()); - } + this->send_layer3_frame(c, FrameInfo::Protocol::IPV4, w.str()); } } @@ -863,8 +1255,7 @@ void IPStackSimulator::on_client_tcp_frame( } } -void IPStackSimulator::open_server_connection( - shared_ptr c, IPClient::TCPConnection& conn) { +void IPStackSimulator::open_server_connection(shared_ptr c, IPClient::TCPConnection& conn) { if (conn.server_bev.get()) { throw logic_error("server connection is already open"); } @@ -913,20 +1304,25 @@ void IPStackSimulator::open_server_connection( } } -void IPStackSimulator::send_pending_push_frame( - shared_ptr c, IPClient::TCPConnection& conn) { +void IPStackSimulator::send_pending_push_frame(shared_ptr 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(pending_bytes, conn.next_push_max_frame_size); + if ((c->link_type == FrameInfo::LinkType::HDLC) && (bytes_to_send > 200)) { + // There is a bug in Dolphin's modem implementation (which I wrote, so it's + // my fault) that causes commands to be dropped when too much data is sent + // at once. To work around this, we only send up to 200 bytes in each push + // frame. + bytes_to_send = 200; + } ip_stack_simulator_log.debug("Sending PSH frame with seq_num %08" PRIX32 ", 0x%zX/0x%zX data bytes", conn.acked_server_seq, bytes_to_send, pending_bytes); - this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn.pending_data.get(), - bytes_to_send); + 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); @@ -953,11 +1349,6 @@ void IPStackSimulator::send_tcp_frame( throw logic_error("data should be given if and only if PSH is given"); } - EthernetHeader ether; - ether.dest_mac = c->mac_addr; - ether.src_mac = this->host_mac_address_bytes; - ether.protocol = 0x0800; // IPv4 - IPv4Header ipv4; ipv4.version_ihl = 0x45; ipv4.tos = 0; @@ -984,28 +1375,16 @@ void IPStackSimulator::send_tcp_frame( 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); + 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)); + StringWriter w; + w.put(ipv4); + w.put(tcp); if (src_bytes) { - evbuffer_add(out_buf, linear_data, src_bytes); + w.write(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()); - } + this->send_layer3_frame(c, FrameInfo::Protocol::IPV4, w.str()); } void IPStackSimulator::dispatch_on_resend_push(evutil_socket_t, short, void* ctx) { diff --git a/src/IPStackSimulator.hh b/src/IPStackSimulator.hh index 2456d362..66bb37a4 100644 --- a/src/IPStackSimulator.hh +++ b/src/IPStackSimulator.hh @@ -21,10 +21,10 @@ public: std::shared_ptr state); ~IPStackSimulator(); - void listen(const std::string& name, const std::string& socket_path); - void listen(const std::string& name, const std::string& addr, int port); - void listen(const std::string& name, int port); - void add_socket(const std::string& name, int fd); + void listen(const std::string& name, const std::string& socket_path, FrameInfo::LinkType link_type); + void listen(const std::string& name, const std::string& addr, int port, FrameInfo::LinkType link_type); + void listen(const std::string& name, int port, FrameInfo::LinkType link_type); + void add_socket(const std::string& name, int fd, FrameInfo::LinkType link_type); static uint32_t connect_address_for_remote_address(uint32_t remote_addr); @@ -41,7 +41,10 @@ private: std::weak_ptr sim; unique_bufferevent bev; - parray mac_addr; + FrameInfo::LinkType link_type; + uint32_t hdlc_escape_control_character_flags = 0xFFFFFFFF; + uint32_t hdlc_remote_magic_number = 0; + parray mac_addr; // Only used for LinkType::ETHERNET uint32_t ipv4_addr; struct TCPConnection { @@ -77,7 +80,7 @@ private: unique_event idle_timeout_event; - IPClient(std::shared_ptr sim, struct bufferevent* bev); + IPClient(std::shared_ptr sim, FrameInfo::LinkType link_type, struct bufferevent* bev); static void dispatch_on_idle_timeout(evutil_socket_t fd, short events, void* ctx); void on_idle_timeout(); @@ -85,10 +88,12 @@ private: struct ListeningSocket { std::string name; + FrameInfo::LinkType link_type; unique_listener listener; - ListeningSocket(const std::string& name, unique_listener&& l) + ListeningSocket(const std::string& name, FrameInfo::LinkType link_type, unique_listener&& l) : name(name), + link_type(link_type), listener(std::move(l)) {} }; @@ -122,7 +127,13 @@ private: static void dispatch_on_client_error(struct bufferevent* bev, short events, void* ctx); void on_client_error(struct bufferevent* bev, short events); + void send_layer3_frame(std::shared_ptr c, FrameInfo::Protocol proto, const std::string& data) const; + void send_layer3_frame(std::shared_ptr c, FrameInfo::Protocol proto, const void* data, size_t size) const; + void on_client_frame(std::shared_ptr c, const std::string& frame); + void on_client_lcp_frame(std::shared_ptr c, const FrameInfo& fi); + void on_client_pap_frame(std::shared_ptr c, const FrameInfo& fi); + void on_client_ipcp_frame(std::shared_ptr c, const FrameInfo& fi); void on_client_arp_frame(std::shared_ptr c, const FrameInfo& fi); void on_client_udp_frame(std::shared_ptr c, const FrameInfo& fi); void on_client_tcp_frame(std::shared_ptr c, const FrameInfo& fi); diff --git a/src/Main.cc b/src/Main.cc index 9b1e61be..19de9546 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1767,13 +1767,18 @@ Action a_run_server_replay_log( } } - if (!state->ip_stack_addresses.empty()) { - config_log.info("Starting IP stack simulator"); + if (!state->ip_stack_addresses.empty() || !state->ppp_stack_addresses.empty()) { + config_log.info("Starting IP/PPP stack simulator"); ip_stack_simulator = make_shared(base, state); for (const auto& it : state->ip_stack_addresses) { auto netloc = parse_netloc(it); string spec = (netloc.second == 0) ? ("T-IPS-" + netloc.first) : string_printf("T-IPS-%hu", netloc.second); - ip_stack_simulator->listen(spec, netloc.first, netloc.second); + ip_stack_simulator->listen(spec, netloc.first, netloc.second, FrameInfo::LinkType::ETHERNET); + } + for (const auto& it : state->ppp_stack_addresses) { + auto netloc = parse_netloc(it); + string spec = (netloc.second == 0) ? ("T-PPPS-" + netloc.first) : string_printf("T-PPPS-%hu", netloc.second); + ip_stack_simulator->listen(spec, netloc.first, netloc.second, FrameInfo::LinkType::HDLC); } } } diff --git a/src/ServerState.cc b/src/ServerState.cc index 2b264802..ea6ca914 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -581,6 +581,16 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { } } catch (const out_of_range&) { } + try { + for (const auto& item : json.at("PPPStackListen").as_list()) { + if (item->is_int()) { + this->ppp_stack_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int())); + } else { + this->ppp_stack_addresses.emplace_back(item->as_string()); + } + } + } catch (const out_of_range&) { + } } auto local_address_str = json.at("LocalAddress").as_string(); diff --git a/src/ServerState.hh b/src/ServerState.hh index 8e8a567b..8c4ab793 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -65,6 +65,7 @@ struct ServerState : public std::enable_shared_from_this { std::string username; uint16_t dns_server_port; std::vector ip_stack_addresses; + std::vector ppp_stack_addresses; bool ip_stack_debug; bool allow_unregistered_users; bool allow_dc_pc_games; diff --git a/system/config.example.json b/system/config.example.json index 81adfaa0..db562bb5 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -128,19 +128,20 @@ "bb-data2": [12005, "bb", "login_server"], }, - // 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. You can also add numbers or "address:port" strings to - // this list to listen for tapserver connections on a TCP port. + // Where to listen for IP and PPP stack clients. This exists to interface with + // PSO GC clients running in a local Dolphin emulator. To enable local Dolphin + // clients to connect, set IPStackListen 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. You can also add numbers or "address:port" + // strings to these lists to listen for tapserver connections on TCP ports. // On Windows, Unix sockets are not available, so you can only use TCP sockets // here. You can get Dolphin to connect locally by adding a port to this list // and configuring Dolphin to connect to the same port. For example, you could // set this to ["127.0.0.1:5059"], and configure Dolphin's tapserver BBA to // connect to 127.0.0.1:5059. "IPStackListen": [], + "PPPStackListen": [], // Other servers to support proxying to. If this is empty for any game // version, the proxy server is disabled for that version. Entries in these diff --git a/tests/config.json b/tests/config.json index a1795316..34ead5e7 100644 --- a/tests/config.json +++ b/tests/config.json @@ -18,6 +18,7 @@ "DNSServerPort": 0, "IPStackListen": [], + "PPPStackListen": [], "EnableItemTracking": true, "Episode3BehaviorFlags": 0xFA,