dtls_host.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. #include "dtls_host.h"
  2. #include <Windows.h>
  3. #include <array>
  4. #include <atomic>
  5. #include <type_traits>
  6. #include "../../../middleware/crypto/ecc_p224.h"
  7. #include "../../../middleware/crypto/random_bytes.h"
  8. #include "../../../middleware/gameplay/dtls/association_keys.h"
  9. #include "../../../middleware/gameplay/dtls/dtls_messages.h"
  10. #include "../../../middleware/gameplay/dtls/record.h"
  11. #include "../../../middleware/gameplay/dtls/replay_high_water.h"
  12. #include "../endpoint/gameplay_endpoint.h"
  13. #include "../gameplay_log.h"
  14. #include "../peer/peer_transport.h"
  15. namespace sunrise::server::gameplay::dtls {
  16. namespace {
  17. namespace wire = middleware::gameplay::dtls;
  18. /** Concurrent associations. One citizen join needs one, and a retry reuses its endpoint. */
  19. constexpr std::size_t kAssociationCapacity = 8;
  20. /** An association that never reaches the key exchange is dropped after this many milliseconds. */
  21. constexpr std::uint64_t kHandshakeTimeout = 30000;
  22. /** How far one association has progressed. */
  23. enum class Stage : std::uint8_t { absent, cookieWait, established };
  24. /** One peer's handshake state. */
  25. struct Association {
  26. state::gameplay::Endpoint endpoint{};
  27. Stage stage{Stage::absent};
  28. /** Tag the peer chose. Every packet this host sends must name it. */
  29. std::uint16_t requesterTag{};
  30. /** Tag this host chose. The peer names it once it has the init ack. */
  31. std::uint16_t responderTag{};
  32. /** Id the peer routes on. Every packet must repeat it or the peer never sees it. */
  33. wire::SecurityId securityId{};
  34. /** The init ack exactly as it left, which the cookie echo has to return unaltered. */
  35. std::array<std::byte, wire::kInitAckSize> issued{};
  36. /** Keys and tag every record of this association uses. */
  37. middleware::gameplay::dtls::RecordContext record{};
  38. /** Authenticated record sequences already admitted on this association. */
  39. middleware::gameplay::dtls::ReplayHighWater receiveHighWater{};
  40. /** False until one received record names the digest that authenticates it. */
  41. bool authKnown{};
  42. /** Sequence the next sent record carries. */
  43. std::uint32_t sendSequence{1};
  44. /** Tick the handshake last advanced. */
  45. std::uint64_t touched{};
  46. /** Order this association reached `established`. */
  47. std::uint64_t opened{};
  48. /** Order one record last arrived on it. The peer reads where it writes. */
  49. std::uint64_t heard{};
  50. };
  51. /** Stamps `opened` and `heard`. They only have to order associations, so neither is a clock. */
  52. std::uint64_t g_openClock{0};
  53. /** Join key the descriptor advertises. The derivation mixes it in. */
  54. constexpr std::array<std::byte, middleware::gameplay::dtls::kSecurityKeySize> kJoinKey{};
  55. /** Record arrivals reported per run. Enough to show the framing without a flood filling the log. */
  56. constexpr unsigned kMaxRecordReports = 24;
  57. /** Message type of a data record. */
  58. constexpr std::uint8_t kRecordType = 6;
  59. std::atomic<unsigned> g_recordReported{0};
  60. std::array<Association, kAssociationCapacity> g_associations{};
  61. /** @return True when both endpoints name the same address and port. */
  62. [[nodiscard]] bool same_endpoint(const state::gameplay::Endpoint& left,
  63. const state::gameplay::Endpoint& right) noexcept {
  64. return left.address == right.address && left.port == right.port;
  65. }
  66. /**
  67. * Finds the association one init belongs to, or takes a slot for it.
  68. * The peer holds several at once, so an established one is displaced only as a last resort.
  69. * @param from Source endpoint.
  70. * @param securityId Security id the init named.
  71. * @param now Monotonic tick count in milliseconds.
  72. * @return The slot, or null when every slot is a live association for another security id.
  73. */
  74. [[nodiscard]] Association* acquire(const state::gameplay::Endpoint& from,
  75. const wire::SecurityId& securityId,
  76. std::uint64_t now) noexcept {
  77. Association* free = nullptr;
  78. Association* oldest = nullptr;
  79. for (Association& association : g_associations) {
  80. if (association.stage != Stage::absent && same_endpoint(association.endpoint, from)
  81. && association.securityId == securityId) {
  82. // The peer restarts its own handshake on every retry, so its own id reuses the slot.
  83. return &association;
  84. }
  85. if (free == nullptr
  86. && (association.stage == Stage::absent
  87. || (association.stage == Stage::cookieWait
  88. && now - association.touched > kHandshakeTimeout))) {
  89. free = &association;
  90. }
  91. if (association.stage == Stage::established
  92. && (oldest == nullptr || association.touched < oldest->touched)) {
  93. oldest = &association;
  94. }
  95. }
  96. // Nothing free, so the least recently used established association goes.
  97. return free != nullptr ? free : oldest;
  98. }
  99. /**
  100. * Finds the association one received record is addressed to.
  101. * @param from Source endpoint.
  102. * @param tag Tag the record names, which is the one this host chose for that association.
  103. * @return The established association, or null.
  104. */
  105. [[nodiscard]] Association* find_addressed(const state::gameplay::Endpoint& from,
  106. std::uint16_t tag) noexcept {
  107. for (Association& association : g_associations) {
  108. if (association.stage == Stage::established && same_endpoint(association.endpoint, from)
  109. && association.responderTag == tag) {
  110. return &association;
  111. }
  112. }
  113. return nullptr;
  114. }
  115. /**
  116. * Finds the association this host sends on for one endpoint.
  117. * The peer reads where it writes, so a reply goes on the association its records last arrived on.
  118. * @param to Peer endpoint.
  119. * @return The established association the peer last used, or null.
  120. */
  121. [[nodiscard]] Association* find_sending(const state::gameplay::Endpoint& to) noexcept {
  122. Association* chosen = nullptr;
  123. for (Association& association : g_associations) {
  124. if (association.stage != Stage::established || !same_endpoint(association.endpoint, to)) {
  125. continue;
  126. }
  127. // Both stamps start at zero, so a fresh association wins only until a record arrives.
  128. if (chosen == nullptr || association.heard > chosen->heard
  129. || (association.heard == chosen->heard && association.opened > chosen->opened)) {
  130. chosen = &association;
  131. }
  132. }
  133. return chosen;
  134. }
  135. /** @return The association for one endpoint whose handshake is still open, or null. */
  136. [[nodiscard]] Association* find_handshake(const state::gameplay::Endpoint& from,
  137. const wire::SecurityId& securityId) noexcept {
  138. for (Association& association : g_associations) {
  139. if (association.stage != Stage::absent && same_endpoint(association.endpoint, from)
  140. && association.securityId == securityId) {
  141. return &association;
  142. }
  143. }
  144. return nullptr;
  145. }
  146. /**
  147. * Draws one nonzero 16-bit tag.
  148. * @param output Receives the tag only on success.
  149. * @return True when Windows produced the bytes.
  150. */
  151. [[nodiscard]] bool generate_tag(std::uint16_t& output) noexcept {
  152. /** Bits in one byte. */
  153. constexpr unsigned kByteBits = 8;
  154. std::array<std::byte, sizeof(std::uint16_t)> bytes{};
  155. if (!middleware::crypto::random::fill(bytes)) {
  156. return false;
  157. }
  158. const auto value =
  159. static_cast<std::uint16_t>(std::to_integer<std::uint16_t>(bytes[0])
  160. | (std::to_integer<std::uint16_t>(bytes[1]) << kByteBits));
  161. // A zero tag reads as "no association" on the peer's side.
  162. output = value == 0 ? 1 : value;
  163. return true;
  164. }
  165. /**
  166. * Answers one init with an init ack.
  167. * @param from Source endpoint.
  168. * @param datagram Received bytes.
  169. * @param now Monotonic tick count in milliseconds.
  170. */
  171. void on_init(const state::gameplay::Endpoint& from,
  172. std::span<const std::byte> datagram,
  173. std::uint64_t now) noexcept {
  174. wire::Init init{};
  175. if (!wire::read_init(datagram, init)) {
  176. report(core::log::Level::warn,
  177. "ev=gameplay stage=dtls result=drop reason=init_decode bytes=%zu",
  178. datagram.size());
  179. return;
  180. }
  181. Association* association = acquire(from, init.securityId, now);
  182. std::uint16_t responderTag = 0;
  183. if (association == nullptr || !generate_tag(responderTag)) {
  184. report(core::log::Level::warn,
  185. "ev=gameplay stage=dtls result=drop reason=%s",
  186. association == nullptr ? "no_slot" : "no_random");
  187. return;
  188. }
  189. wire::InitAck initAck{};
  190. initAck.requesterTag = init.initTag;
  191. initAck.responderTag = responderTag;
  192. initAck.address = from.address;
  193. initAck.port = from.port;
  194. initAck.timestamp = static_cast<std::uint32_t>(now);
  195. initAck.securityId = init.securityId;
  196. // The cookie is only ever compared with the copy kept here, so a random value serves.
  197. if (!middleware::crypto::random::fill(initAck.cookie)) {
  198. report(core::log::Level::warn, "ev=gameplay stage=dtls result=drop reason=no_random");
  199. return;
  200. }
  201. std::array<std::byte, wire::kInitAckSize> encoded{};
  202. wire::write_init_ack(initAck, encoded);
  203. *association = {};
  204. association->endpoint = from;
  205. association->stage = Stage::cookieWait;
  206. association->requesterTag = init.initTag;
  207. association->responderTag = responderTag;
  208. association->securityId = init.securityId;
  209. association->issued = encoded;
  210. association->touched = now;
  211. const bool sent = endpoint::send_to(from, encoded);
  212. report(core::log::Level::info,
  213. "ev=gameplay stage=dtls result=%s step=init_ack peer_tag=0x%04X local_tag=0x%04X",
  214. sent ? "ok" : "send_failed",
  215. static_cast<unsigned>(init.initTag),
  216. static_cast<unsigned>(responderTag));
  217. }
  218. /**
  219. * Answers one cookie echo.
  220. * @param from Source endpoint.
  221. * @param datagram Received bytes.
  222. * @param now Monotonic tick count in milliseconds.
  223. */
  224. void on_cookie_echo(const state::gameplay::Endpoint& from,
  225. std::span<const std::byte> datagram,
  226. std::uint64_t now) noexcept {
  227. wire::CookieEcho echo{};
  228. if (!wire::read_cookie_echo(datagram, echo)) {
  229. report(core::log::Level::warn,
  230. "ev=gameplay stage=dtls result=drop reason=cookie_decode bytes=%zu",
  231. datagram.size());
  232. return;
  233. }
  234. Association* association = find_handshake(from, echo.securityId);
  235. if (association == nullptr) {
  236. report(core::log::Level::warn, "ev=gameplay stage=dtls result=drop reason=no_association");
  237. return;
  238. }
  239. // The peer returns the init ack whole, so comparing it covers the cookie and its bound fields.
  240. if (echo.echoedInitAck != association->issued) {
  241. report(core::log::Level::warn, "ev=gameplay stage=dtls result=drop reason=cookie_mismatch");
  242. return;
  243. }
  244. association->touched = now;
  245. middleware::crypto::ecc::Agreement agreement{};
  246. if (!middleware::crypto::ecc::agree(echo.publicKey, agreement)) {
  247. report(core::log::Level::warn, "ev=gameplay stage=dtls result=drop reason=key_agreement");
  248. return;
  249. }
  250. const bool derived = middleware::gameplay::dtls::derive(
  251. agreement.sharedSecret, kJoinKey, association->record.keys);
  252. SecureZeroMemory(agreement.sharedSecret.data(), agreement.sharedSecret.size());
  253. if (!derived) {
  254. report(core::log::Level::warn, "ev=gameplay stage=dtls result=drop reason=key_derivation");
  255. return;
  256. }
  257. // Every record names the tag the peer chose for itself.
  258. association->record.sendTag = association->requesterTag;
  259. wire::CookieAck cookieAck{};
  260. cookieAck.requesterTag = association->requesterTag;
  261. cookieAck.securityId = association->securityId;
  262. cookieAck.publicKey = agreement.publicKey;
  263. std::array<std::byte, wire::kCookieAckSize> encoded{};
  264. wire::write_cookie_ack(cookieAck, encoded);
  265. const bool sent = endpoint::send_to(from, encoded);
  266. association->stage = sent ? Stage::established : Stage::cookieWait;
  267. if (sent) {
  268. ++g_openClock;
  269. association->opened = g_openClock;
  270. }
  271. report(core::log::Level::info,
  272. "ev=gameplay stage=dtls result=%s step=cookie_ack peer_tag=0x%04X",
  273. sent ? "ok" : "send_failed",
  274. static_cast<unsigned>(association->requesterTag));
  275. }
  276. /**
  277. * Opens one received record and hands its payload to the peer transport.
  278. * @param from Source endpoint.
  279. * @param datagram Received bytes.
  280. * @param now Monotonic tick count in milliseconds.
  281. */
  282. void on_record(const state::gameplay::Endpoint& from,
  283. std::span<const std::byte> datagram,
  284. std::uint64_t now) noexcept {
  285. // One endpoint carries one association per security id, so the record's own tag picks it.
  286. std::uint16_t addressed = 0;
  287. if (!middleware::gameplay::dtls::read_record_tag(datagram, addressed)) {
  288. return;
  289. }
  290. Association* association = find_addressed(from, addressed);
  291. if (association == nullptr) {
  292. return;
  293. }
  294. // The peer's digest choice is not announced, so the first record it sends names it.
  295. if (!association->authKnown) {
  296. if (!middleware::gameplay::dtls::identify_auth(
  297. association->record.keys, datagram, association->record.authAlgorithm)) {
  298. if (g_recordReported.fetch_add(1, std::memory_order_relaxed) < kMaxRecordReports) {
  299. report(core::log::Level::warn,
  300. "ev=gameplay stage=dtls result=drop reason=auth_unknown bytes=%zu",
  301. datagram.size());
  302. }
  303. return;
  304. }
  305. association->authKnown = true;
  306. report(core::log::Level::info,
  307. "ev=gameplay stage=dtls result=ok step=auth digest=%u",
  308. static_cast<unsigned>(association->record.authAlgorithm));
  309. }
  310. std::array<std::byte, middleware::gameplay::dtls::kRecordCapacity> payload{};
  311. std::size_t size = 0;
  312. std::uint32_t sequence = 0;
  313. if (!middleware::gameplay::dtls::open(association->record, datagram, payload, size, sequence)) {
  314. if (g_recordReported.fetch_add(1, std::memory_order_relaxed) < kMaxRecordReports) {
  315. report(core::log::Level::warn,
  316. "ev=gameplay stage=dtls result=drop reason=record bytes=%zu",
  317. datagram.size());
  318. }
  319. return;
  320. }
  321. const wire::ReplayDecision replay = wire::update(association->receiveHighWater, sequence);
  322. if (replay != wire::ReplayDecision::accepted) {
  323. if (g_recordReported.fetch_add(1, std::memory_order_relaxed) < kMaxRecordReports) {
  324. report(core::log::Level::warn,
  325. "ev=gameplay stage=dtls result=drop reason=%s seq=%u",
  326. replay == wire::ReplayDecision::duplicate ? "replay_duplicate" : "replay_old",
  327. sequence);
  328. }
  329. return;
  330. }
  331. association->touched = now;
  332. ++g_openClock;
  333. association->heard = g_openClock;
  334. if (g_recordReported.fetch_add(1, std::memory_order_relaxed) < kMaxRecordReports) {
  335. report(core::log::Level::info,
  336. "ev=gameplay stage=dtls result=ok step=record seq=%u bytes=%zu",
  337. sequence,
  338. size);
  339. }
  340. peer::deliver(from, {payload.data(), size}, now);
  341. }
  342. } // namespace
  343. /** Seals one transport payload and sends it to an established association. */
  344. bool send_payload(const state::gameplay::Endpoint& to,
  345. std::span<const std::byte> payload) noexcept {
  346. Association* association = find_sending(to);
  347. if (association == nullptr) {
  348. return false;
  349. }
  350. std::array<std::byte, middleware::gameplay::dtls::kRecordCapacity> datagram{};
  351. std::size_t size = 0;
  352. if (!middleware::gameplay::dtls::seal(
  353. association->record, association->sendSequence, payload, datagram, size)) {
  354. report(core::log::Level::warn, "ev=gameplay stage=dtls result=fail step=seal");
  355. return false;
  356. }
  357. ++association->sendSequence;
  358. return endpoint::send_to(to, {datagram.data(), size});
  359. }
  360. /** Answers one association handshake datagram. */
  361. bool route(const state::gameplay::Endpoint& from,
  362. std::span<const std::byte> datagram,
  363. std::uint64_t now) noexcept {
  364. std::uint8_t type = 0;
  365. if (!wire::read_type(datagram, type)) {
  366. return false;
  367. }
  368. if (type == static_cast<std::uint8_t>(wire::Type::init)) {
  369. on_init(from, datagram, now);
  370. return true;
  371. }
  372. if (type == static_cast<std::uint8_t>(wire::Type::cookieEcho)) {
  373. on_cookie_echo(from, datagram, now);
  374. return true;
  375. }
  376. if (type == kRecordType) {
  377. on_record(from, datagram, now);
  378. return true;
  379. }
  380. return false;
  381. }
  382. /** Drops every association and clears its key material. */
  383. void reset() noexcept {
  384. // An assignment can be elided, and the table holds derived keys.
  385. static_assert(std::is_trivially_copyable_v<Association>, "the table is erased as raw bytes");
  386. SecureZeroMemory(g_associations.data(), sizeof(g_associations));
  387. g_openClock = 0;
  388. }
  389. } // namespace sunrise::server::gameplay::dtls