ntptun/tests/test_carrier.cpp
2026-07-24 21:05:01 +02:00

147 lines
5.5 KiB
C++

#include "ntptun/Carrier.hpp"
#include "ntptun/Bytes.hpp"
#include "ntptun/KeyStore.hpp"
#include "TestUtil.hpp"
using namespace ntptun;
namespace {
Key make_key(Byte seed) {
Key k{};
for (std::size_t i = 0; i < k.size(); ++i) {
k[i] = static_cast<Byte>(seed + i);
}
return k;
}
// Minimal syntactically valid IPv4 datagram whose Total Length equals its size.
ByteVector make_ipv4(std::size_t len) {
ByteVector p(len, 0);
p[0] = 0x45; // version 4, IHL 5
store_be16(p.data() + 2, static_cast<std::uint16_t>(len));
// A recognizable source address for routing tests.
p[12] = 10; p[13] = 0; p[14] = 0; p[15] = 7;
return p;
}
KeyStore store_with_default(const Key& k) {
KeyStore ks;
ks.set_default_key(k);
return ks;
}
} // namespace
void run_tests() {
const Key key = make_key(1);
const KeyStore keys = store_with_default(key);
const std::uint16_t client_id = 42;
const std::uint64_t ts = 0x1122334455667788ULL;
// --- Round trip of an IP datagram (client -> server) --------------------
{
const ByteVector inner = make_ipv4(40);
const ByteVector value = encode_carrier_value(
client_id, kKindIpDatagram, inner, key, Direction::ClientToServer, ts);
// Extension value length must be 4-byte aligned.
CHECK_EQ(value.size() % 4, static_cast<std::size_t>(0));
DecodedCarrier out;
const CarrierStatus st = decode_carrier_value(
value, keys, Direction::ClientToServer, ts, out);
CHECK_EQ(st, CarrierStatus::Ok);
CHECK_EQ(out.client_id, client_id);
CHECK_EQ(out.kind, kKindIpDatagram);
CHECK_EQ(out.inner_ip.size(), inner.size());
CHECK(out.inner_ip == inner);
}
// --- Round trip with padding (non-multiple-of-4 inner length) -----------
{
const ByteVector inner = make_ipv4(22); // 22 % 4 == 2 -> 2 pad bytes
ByteVector value = encode_carrier_value(
client_id, kKindIpDatagram, inner, key, Direction::ServerToClient, ts);
DecodedCarrier out;
CHECK_EQ(decode_carrier_value(value, keys, Direction::ServerToClient, ts, out),
CarrierStatus::Ok);
CHECK(out.inner_ip == inner);
// Tampering with any sealed byte fails Poly1305 authentication; an
// unauthenticated carrier is indistinguishable from unrelated traffic
// and therefore takes the relay path.
value.back() ^= 0xFF;
CHECK_EQ(decode_carrier_value(value, keys, Direction::ServerToClient, ts, out),
CarrierStatus::NotTunnel);
}
// --- Empty poll ---------------------------------------------------------
{
const ByteVector value = encode_carrier_value(
client_id, kKindEmptyPoll, ByteSpan{}, key, Direction::ClientToServer, ts);
DecodedCarrier out;
CHECK_EQ(decode_carrier_value(value, keys, Direction::ClientToServer, ts, out),
CarrierStatus::Ok);
CHECK_EQ(out.kind, kKindEmptyPoll);
CHECK(out.inner_ip.empty());
}
// --- Wrong key => not our tunnel (relay path) ---------------------------
{
const ByteVector inner = make_ipv4(40);
const ByteVector value = encode_carrier_value(
client_id, kKindIpDatagram, inner, key, Direction::ClientToServer, ts);
const KeyStore other = store_with_default(make_key(99));
DecodedCarrier out;
CHECK_EQ(decode_carrier_value(value, other, Direction::ClientToServer, ts, out),
CarrierStatus::NotTunnel);
}
// --- Wrong direction / wrong timestamp => not our tunnel ----------------
{
const ByteVector inner = make_ipv4(40);
const ByteVector value = encode_carrier_value(
client_id, kKindIpDatagram, inner, key, Direction::ClientToServer, ts);
DecodedCarrier out;
CHECK_EQ(decode_carrier_value(value, keys, Direction::ServerToClient, ts, out),
CarrierStatus::NotTunnel);
CHECK_EQ(decode_carrier_value(value, keys, Direction::ClientToServer, ts + 1, out),
CarrierStatus::NotTunnel);
}
// --- No key configured => relay path ------------------------------------
{
const ByteVector inner = make_ipv4(40);
const ByteVector value = encode_carrier_value(
client_id, kKindIpDatagram, inner, key, Direction::ClientToServer, ts);
KeyStore empty;
DecodedCarrier out;
CHECK_EQ(decode_carrier_value(value, empty, Direction::ClientToServer, ts, out),
CarrierStatus::NotTunnel);
}
// --- Per-client key isolation -------------------------------------------
{
KeyStore ks;
ks.set_client_key(42, make_key(1));
ks.set_client_key(43, make_key(2));
const ByteVector inner = make_ipv4(40);
// Encoded for client 42 with its key.
const ByteVector value = encode_carrier_value(
42, kKindIpDatagram, inner, make_key(1), Direction::ClientToServer, ts);
DecodedCarrier out;
CHECK_EQ(decode_carrier_value(value, ks, Direction::ClientToServer, ts, out),
CarrierStatus::Ok);
CHECK_EQ(out.client_id, static_cast<std::uint16_t>(42));
// Same bytes but claiming client 43 (id field is clear) resolves the
// wrong key, so it is not accepted as a tunnel carrier.
ByteVector spoof = value;
store_be16(spoof.data(), 43);
CHECK_EQ(decode_carrier_value(spoof, ks, Direction::ClientToServer, ts, out),
CarrierStatus::NotTunnel);
}
}
TEST_MAIN()