#include "../types.h" std::unique_ptr devilution_net::inst; const devilution_net_udp::endpoint devilution_net_udp::none; static constexpr bool disable_encryption = false; void devilution_net_udp::packet::create(devilution_net_udp::packet_type t, devilution_net_udp::plr_t s, devilution_net_udp::plr_t d, devilution_net_udp::buffer_t m) { if (have_encrypted || have_decrypted) ABORT(); if (t != PT_MESSAGE) ABORT(); have_decrypted = true; m_type = t; m_src = s; m_dest = d; m_message = std::move(m); } void devilution_net_udp::packet::create(devilution_net_udp::packet_type t, devilution_net_udp::plr_t s, devilution_net_udp::plr_t d, devilution_net_udp::turn_t u) { if (have_encrypted || have_decrypted) ABORT(); if (t != PT_TURN) ABORT(); have_decrypted = true; m_type = t; m_src = s; m_dest = d; m_turn = u; } void devilution_net_udp::packet::create(devilution_net_udp::packet_type t, devilution_net_udp::plr_t s, devilution_net_udp::plr_t d, devilution_net_udp::cookie_t c) { if (have_encrypted || have_decrypted) ABORT(); if (t != PT_JOIN_REQUEST) ABORT(); have_decrypted = true; m_type = t; m_src = s; m_dest = d; m_cookie = c; } void devilution_net_udp::packet::create(devilution_net_udp::packet_type t, devilution_net_udp::plr_t s, devilution_net_udp::plr_t d, devilution_net_udp::cookie_t c, devilution_net_udp::plr_t n) { if (have_encrypted || have_decrypted) ABORT(); if (t != PT_JOIN_ACCEPT) ABORT(); have_decrypted = true; m_type = t; m_src = s; m_dest = d; m_cookie = c; m_newplr = n; } void devilution_net_udp::packet::create(devilution_net_udp::packet_type t, devilution_net_udp::plr_t s, devilution_net_udp::plr_t d, devilution_net_udp::plr_t o) { if (have_encrypted || have_decrypted) ABORT(); if (t != PT_LEAVE_GAME) ABORT(); have_decrypted = true; m_type = t; m_src = s; m_dest = d; m_oldplr = o; } void devilution_net_udp::packet::create(devilution_net_udp::buffer_t buf) { if (have_encrypted || have_decrypted) ABORT(); encrypted_buffer = std::move(buf); have_encrypted = true; } const devilution_net_udp::buffer_t &devilution_net_udp::packet::data() { if (!have_decrypted || !have_encrypted) ABORT(); return encrypted_buffer; } devilution_net_udp::packet_type devilution_net_udp::packet::type() { if (!have_decrypted) ABORT(); return m_type; } devilution_net_udp::plr_t devilution_net_udp::packet::src() { if (!have_decrypted) ABORT(); return m_src; } devilution_net_udp::plr_t devilution_net_udp::packet::dest() { if (!have_decrypted) ABORT(); return m_dest; } const devilution_net_udp::buffer_t &devilution_net_udp::packet::message() { if (!have_decrypted) ABORT(); if (m_type != PT_MESSAGE) throw devilution_net_udp::packet_exception(); return m_message; } devilution_net_udp::turn_t devilution_net_udp::packet::turn() { if (!have_decrypted) ABORT(); if (m_type != PT_TURN) throw devilution_net_udp::packet_exception(); return m_turn; } devilution_net_udp::cookie_t devilution_net_udp::packet::cookie() { if (!have_decrypted) ABORT(); if (m_type != PT_JOIN_REQUEST && m_type != PT_JOIN_ACCEPT) throw devilution_net_udp::packet_exception(); return m_cookie; } devilution_net_udp::plr_t devilution_net_udp::packet::newplr() { if (!have_decrypted) ABORT(); if (m_type != PT_JOIN_ACCEPT) throw devilution_net_udp::packet_exception(); return m_newplr; } devilution_net_udp::plr_t devilution_net_udp::packet::oldplr() { if (!have_decrypted) ABORT(); if (m_type != PT_LEAVE_GAME) throw devilution_net_udp::packet_exception(); return m_oldplr; } void devilution_net_udp::packet::encrypt() { if (!have_decrypted) ABORT(); if (have_encrypted) return; process_data(); if (!disable_encryption) { auto len_cleartext = encrypted_buffer.size(); encrypted_buffer.insert(encrypted_buffer.begin(), crypto_secretbox_NONCEBYTES, 0); encrypted_buffer.insert(encrypted_buffer.end(), crypto_secretbox_MACBYTES, 0); randombytes_buf(encrypted_buffer.data(), crypto_secretbox_NONCEBYTES); if (crypto_secretbox_easy(encrypted_buffer.data() + crypto_secretbox_NONCEBYTES, encrypted_buffer.data() + crypto_secretbox_NONCEBYTES, len_cleartext, encrypted_buffer.data(), key.data())) ABORT(); } have_encrypted = true; } void devilution_net_udp::packet::decrypt() { if (!have_encrypted) ABORT(); if (have_decrypted) return; if (!disable_encryption) { if (encrypted_buffer.size() < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES + sizeof(packet_type) + 2 * sizeof(plr_t)) throw devilution_net_udp::packet_exception(); auto pktlen = encrypted_buffer.size() - crypto_secretbox_NONCEBYTES - crypto_secretbox_MACBYTES; decrypted_buffer.resize(pktlen); if (crypto_secretbox_open_easy(decrypted_buffer.data(), encrypted_buffer.data() + crypto_secretbox_NONCEBYTES, encrypted_buffer.size() - crypto_secretbox_NONCEBYTES, encrypted_buffer.data(), key.data())) throw devilution_net_udp::packet_exception(); } else { if (encrypted_buffer.size() < sizeof(packet_type) + 2 * sizeof(plr_t)) throw devilution_net_udp::packet_exception(); decrypted_buffer = encrypted_buffer; } process_data(); have_decrypted = true; } devilution_net_udp::devilution_net_udp() { if (sodium_init() < 0) abort(); } int devilution_net_udp::create(std::string addrstr, std::string passwd) { sock = asio::ip::udp::socket(context); // to be removed later setup_password(passwd); auto ipaddr = asio::ip::make_address(addrstr); if (ipaddr.is_v4()) sock.open(asio::ip::udp::v4()); else if (ipaddr.is_v6()) sock.open(asio::ip::udp::v6()); sock.non_blocking(true); unsigned short port = default_port; /* while(port <= default_port+try_ports) { try { sock.bind(asio::ip::udp::endpoint(asio::ip::address_v6(), port)); } catch (std::exception e) { eprintf("bind: %s, %s\n", asio::ip::address_v6().to_string(), e.what()); } ++port; } */ try { sock.bind(endpoint(ipaddr, port)); } catch (std::exception e) { return -1; } plr_self = 0; return plr_self; } int devilution_net_udp::join(std::string addrstr, std::string passwd) { setup_password(passwd); auto ipaddr = asio::ip::make_address(addrstr); endpoint themaster(ipaddr, default_port); sock.connect(themaster); master = themaster; { // hack: try to join for 5 seconds randombytes_buf(reinterpret_cast(&cookie_self), sizeof(cookie_t)); packet pkt(key); pkt.create(PT_JOIN_REQUEST, ADDR_BROADCAST, ADDR_MASTER, cookie_self); send(pkt); for (auto i = 0; i < 5; ++i) { recv(); if (plr_self != ADDR_BROADCAST) break; // join successful sleep(1); } } return (plr_self == ADDR_BROADCAST ? 4 : plr_self); } void devilution_net_udp::setup_password(std::string pw) { //pw.resize(std::min(pw.size(), crypto_pwhash_PASSWD_MAX)); //pw.resize(std::max(pw.size(), crypto_pwhash_PASSWD_MIN), 0); std::string salt("devilution-salt"); salt.resize(crypto_pwhash_SALTBYTES, 0); if (crypto_pwhash(key.data(), crypto_secretbox_KEYBYTES, pw.data(), pw.size(), reinterpret_cast(salt.data()), crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE, crypto_pwhash_ALG_DEFAULT)) ABORT(); } void devilution_net_udp::recv() { try { while (1) { // read until kernel buffer is empty? try { endpoint sender; buffer_t pkt_buf(max_packet_size); size_t pkt_len; pkt_len = sock.receive_from(asio::buffer(pkt_buf), sender); pkt_buf.resize(pkt_len); packet pkt(key); pkt.create(pkt_buf); pkt.decrypt(); recv_decrypted(pkt, sender); } catch (packet_exception e) { // drop packet } } } catch (std::exception e) { return; } } void devilution_net_udp::send(devilution_net_udp::packet &pkt, endpoint sender) { pkt.encrypt(); for (auto &dest : dests_for_addr(pkt.dest(), sender)) { sock.send_to(asio::buffer(pkt.data()), dest); } } std::set devilution_net_udp::dests_for_addr(plr_t dest, endpoint sender) { auto ret = std::set(); if (dest == plr_self) return ret; if (0 <= dest && dest < MAX_PLRS) { if (active_table[dest]) ret.insert(nexthop_table[dest]); } else if (dest == ADDR_BROADCAST) { for (auto i = 0; i < MAX_PLRS; ++i) if (i != plr_self && active_table[i]) ret.insert(nexthop_table[i]); ret.insert(connection_requests_pending.begin(), connection_requests_pending.end()); } else if (dest == ADDR_MASTER) { if (master != none) ret.insert(master); } ret.erase(sender); return ret; } void devilution_net_udp::handle_join_request(packet &pkt, endpoint sender) { plr_t i; for (i = 0; i < MAX_PLRS; ++i) { if (i != plr_self && nexthop_table[i] == none) { nexthop_table[i] = sender; break; } } packet reply(key); reply.create(PT_JOIN_ACCEPT, plr_self, ADDR_BROADCAST, pkt.cookie(), i); send(reply); } void devilution_net_udp::handle_accept(packet &pkt) { if (plr_self != ADDR_BROADCAST) return; // already have player id if (pkt.cookie() == cookie_self) plr_self = pkt.newplr(); } void devilution_net_udp::recv_decrypted(packet &pkt, endpoint sender) { // 1. route send(pkt, sender); // 2. handle local if (pkt.src() == ADDR_BROADCAST && pkt.dest() == ADDR_MASTER) { connection_requests_pending.insert(sender); if (master == none) { handle_join_request(pkt, sender); } } // normal packets if (pkt.src() < 0 || pkt.src() >= MAX_PLRS) return; //drop packet if (active_table[pkt.src()]) { //WRONG?!? if (sender != nexthop_table[pkt.src()]) return; //rpfilter fail: drop packet } else { nexthop_table[pkt.src()] = sender; // new connection: accept } active_table[pkt.src()] = ACTIVE; if (pkt.dest() != plr_self && pkt.dest() != ADDR_BROADCAST) return; //packet not for us, drop switch (pkt.type()) { case PT_MESSAGE: message_queue.push(message_t(pkt.src(), pkt.message())); break; case PT_TURN: turn_last[pkt.src()] = pkt.turn(); turn_new[pkt.src()] = true; break; case PT_JOIN_ACCEPT: handle_accept(pkt); break; case PT_LEAVE_GAME: // todo break; // otherwise drop } } bool devilution_net_udp::SNetReceiveMessage(int *sender, char **data, int *size) { recv(); if (message_queue.empty()) return false; message_last = message_queue.front(); message_queue.pop(); *sender = message_last.sender; *size = message_last.payload.size(); *data = reinterpret_cast(message_last.payload.data()); return true; } bool devilution_net_udp::SNetSendMessage(int playerID, void *data, unsigned int size) { if (playerID != SNPLAYER_ALL && playerID != SNPLAYER_OTHERS && (playerID < 0 || playerID >= MAX_PLRS)) abort(); auto raw_message = reinterpret_cast(data); buffer_t message(raw_message, raw_message + size); if (playerID == plr_self || playerID == SNPLAYER_ALL) message_queue.push(message_t(plr_self, message)); plr_t dest; if (playerID == SNPLAYER_ALL || playerID == SNPLAYER_OTHERS) dest = ADDR_BROADCAST; else dest = playerID; packet pkt(key); pkt.create(PT_MESSAGE, plr_self, dest, message); send(pkt); return true; } bool devilution_net_udp::SNetReceiveTurns(char **data, unsigned int *size, DWORD *status) { recv(); for (auto i = 0; i < MAX_PLRS; ++i) { status[i] = 0; if (i == plr_self || nexthop_table[i] != none) { status[i] |= (PS_ACTIVE | PS_CONNECTED); } size[i] = sizeof(turn_t); if (turn_new[i] = true) { status[i] |= PS_HASMSG; data[i] = reinterpret_cast(&turn_last[i]); turn_new[i] = false; } } return true; } bool devilution_net_udp::SNetSendTurn(char *data, unsigned int size) { if (size != sizeof(turn_t)) ABORT(); packet pkt(key); pkt.create(PT_TURN, plr_self, ADDR_BROADCAST, *reinterpret_cast(data)); send(pkt); return true; } int devilution_net_udp::SNetGetProviderCaps(struct _SNETCAPS *caps) { caps->size = 0; // engine writes only ?!? caps->flags = 0; // unused caps->maxmessagesize = 512; // capped to 512; underflow if < 24 caps->maxqueuesize = 0; // unused caps->maxplayers = MAX_PLRS; // capped to 4 caps->bytessec = 1000000; // ? caps->latencyms = 0; // unused caps->defaultturnssec = 10; // ? caps->defaultturnsintransit = 1; // maximum acceptable number of turns in queue? return 1; }