#include #include #include "ipv4.h" #include "wg_config.h" using namespace roro::net; void setUp() {} void tearDown() {} namespace { // Keys made for these tests: they open nothing. const char* const kPriv = "aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789+/aBcDE="; const char* const kPub = "h+vdhuWJykaesw515qrYYGNdg2pGvE7JU5PXIAV2YzE="; const char* const kPsk = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; std::string conf(const std::string& allowed = "10.9.0.0/24", const std::string& more = "") { return std::string("[Interface]\nPrivateKey = ") + kPriv + "\nAddress = 10.9.0.2/24\nDNS = 10.9.0.1\n" + more + "\n[Peer]\nPublicKey = " + kPub + "\nEndpoint = vpn.example.org:51820\nAllowedIPs = " + allowed + "\n"; } uint32_t ip(const char* text) { uint32_t v = 0; TEST_ASSERT_TRUE(parseIpv4(text, v)); return v; } } // namespace void test_a_usual_file() { WgConfig c; TEST_ASSERT_EQUAL_STRING("", parseWgConf(conf(), c).c_str()); TEST_ASSERT_EQUAL_STRING(kPriv, c.privateKey.c_str()); TEST_ASSERT_EQUAL_STRING(kPub, c.peerKey.c_str()); TEST_ASSERT_EQUAL_UINT32(ip("10.9.0.2"), c.address); TEST_ASSERT_EQUAL_INT(24, c.prefix); TEST_ASSERT_EQUAL_UINT32(ip("10.9.0.1"), c.dns[0]); TEST_ASSERT_EQUAL_UINT32(0, c.dns[1]); TEST_ASSERT_EQUAL_STRING("vpn.example.org", c.endpointHost.c_str()); TEST_ASSERT_EQUAL_UINT16(51820, c.endpointPort); TEST_ASSERT_EQUAL_INT(1, c.allowedCount); TEST_ASSERT_EQUAL_INT(25, c.keepalive); // none given: this device is behind a NAT TEST_ASSERT_EQUAL_INT(0, c.mtu); TEST_ASSERT_TRUE(c.presharedKey.empty()); } void test_as_people_write_them() { std::string text = std::string("# my phone's old config\r\n[interface]\r\n privatekey=") + kPriv + " ; secret\r\nAddress = fd00::2/64, 192.168.77.5\r\nDNS = dns.example, 2001:db8::1, 9.9.9.9, 1.1.1.1, 8.8.8.8\r\nMTU = 1280\r\nListenPort = 51820\r\n" "PostUp = iptables -A FORWARD\r\n\r\n[PEER]\r\nPublicKey = " + kPub + "\r\nPresharedKey = " + kPsk + "\r\nEndpoint = 203.0.113.9:4500\r\nAllowedIPs = 0.0.0.0/0, ::/0\r\nPersistentKeepalive = 0\r\n"; WgConfig c; TEST_ASSERT_EQUAL_STRING("", parseWgConf(text, c).c_str()); TEST_ASSERT_EQUAL_UINT32(ip("192.168.77.5"), c.address); // the IPv4 one, and alone it is a /32 TEST_ASSERT_EQUAL_INT(32, c.prefix); TEST_ASSERT_EQUAL_UINT32(ip("9.9.9.9"), c.dns[0]); // names and IPv6 left out, two kept TEST_ASSERT_EQUAL_UINT32(ip("1.1.1.1"), c.dns[1]); TEST_ASSERT_EQUAL_INT(1280, c.mtu); TEST_ASSERT_EQUAL_UINT16(51820, c.listenPort); TEST_ASSERT_EQUAL_STRING(kPsk, c.presharedKey.c_str()); TEST_ASSERT_EQUAL_STRING("203.0.113.9", c.endpointHost.c_str()); TEST_ASSERT_EQUAL_UINT16(4500, c.endpointPort); TEST_ASSERT_EQUAL_INT(1, c.allowedCount); TEST_ASSERT_EQUAL_INT(0, c.allowed[0].prefix); TEST_ASSERT_EQUAL_INT(0, c.keepalive); // said so } void test_it_survives_being_stored() { WgConfig a, b; TEST_ASSERT_EQUAL_STRING("", parseWgConf(conf("10.9.0.0/24, 192.168.1.77/24", std::string("MTU = 1300\nListenPort = 4242\n")), a).c_str()); a.presharedKey = kPsk; std::string stored = toWgConf(a); TEST_ASSERT_EQUAL_STRING("", parseWgConf(stored, b).c_str()); TEST_ASSERT_EQUAL_STRING(stored.c_str(), toWgConf(b).c_str()); TEST_ASSERT_EQUAL_UINT32(ip("192.168.1.0"), b.allowed[1].address); // a range is kept as its network TEST_ASSERT_EQUAL_INT(1300, b.mtu); TEST_ASSERT_EQUAL_UINT16(4242, b.listenPort); TEST_ASSERT_EQUAL_STRING(kPsk, b.presharedKey.c_str()); } void test_what_is_refused_and_why() { WgConfig c; c.endpointHost = "untouched"; auto why = [&](const std::string& text) { return parseWgConf(text, c); }; TEST_ASSERT_EQUAL_STRING("no PrivateKey under [Interface]", why("[Interface]\nAddress = 10.0.0.2/24\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 2: PrivateKey isn't a key", why("[Interface]\nPrivateKey = tooshort=\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 3: Address has no IPv4 address", why(std::string("[Interface]\nPrivateKey = ") + kPriv + "\nAddress = fd00::2/64\n").c_str()); std::string base = std::string("[Interface]\nPrivateKey = ") + kPriv + "\nAddress = 10.0.0.2/24\n[Peer]\nPublicKey = " + kPub + "\n"; TEST_ASSERT_EQUAL_STRING("no Endpoint under [Peer]", why(base + "AllowedIPs = 10.0.0.0/24\n").c_str()); TEST_ASSERT_EQUAL_STRING("no IPv4 range in AllowedIPs", why(base + "Endpoint = a.example:1\nAllowedIPs = ::/0\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 6: an IPv6 Endpoint: IPv4 or a name only", why(base + "Endpoint = [2001:db8::1]:51820\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 6: Endpoint must be host:port", why(base + "Endpoint = vpn.example.org\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 6: AllowedIPs has something that isn't an address range", why(base + "AllowedIPs = 10.0.0.0/33\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 6: AllowedIPs: four IPv4 ranges at most", why(base + "AllowedIPs = 10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24, 10.0.4.0/24\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 8: a second peer: this device has one tunnel to one peer", why(base + "Endpoint = a.example:1\nAllowedIPs = 10.0.0.0/24\n[Peer]\nPublicKey = " + kPub + "\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 1: a setting before [Interface]", why("PrivateKey = x\n").c_str()); TEST_ASSERT_EQUAL_STRING("line 4: MTU must be 576 to 1500", why(std::string("[Interface]\nPrivateKey = ") + kPriv + "\nAddress = 10.0.0.2\nMTU = 9000\n").c_str()); TEST_ASSERT_EQUAL_STRING("untouched", c.endpointHost.c_str()); // a refused file changes nothing // No message carries a key. std::string bad = std::string("[Interface]\nPrivateKey = ") + kPriv + "x\n"; TEST_ASSERT_TRUE(why(bad).find("aBcD") == std::string::npos); } void test_keys() { TEST_ASSERT_TRUE(validWgKey(kPriv)); TEST_ASSERT_TRUE(validWgKey(kPub)); TEST_ASSERT_FALSE(validWgKey("")); TEST_ASSERT_FALSE(validWgKey(std::string(kPub).substr(0, 43))); TEST_ASSERT_FALSE(validWgKey(std::string(kPub).substr(0, 43) + "A")); // no padding TEST_ASSERT_FALSE(validWgKey("h+vdhuWJykaesw515qrYYGNdg2pGvE7JU5PXIAV2Yz!=")); // not base64 TEST_ASSERT_FALSE(validWgKey("h+vdhuWJykaesw515qrYYGNdg2pGvE7JU5PXIAV2YzF=")); // bits past the 32nd byte } void test_what_goes_through_it() { WgConfig c; parseWgConf(conf("10.9.0.0/24"), c); WgRouting r = routingOf(c); TEST_ASSERT_FALSE(r.full); TEST_ASSERT_EQUAL_INT(24, r.prefix); TEST_ASSERT_EQUAL_INT(0, r.unreachable); TEST_ASSERT_TRUE(wgReaches(c, ip("10.9.0.1"))); TEST_ASSERT_FALSE(wgReaches(c, ip("10.9.1.1"))); TEST_ASSERT_FALSE(wgReaches(c, ip("93.184.216.34"))); TEST_ASSERT_EQUAL_STRING("10.9.0.0/24", describeWgRouting(c).c_str()); parseWgConf(conf("0.0.0.0/0"), c); TEST_ASSERT_TRUE(routingOf(c).full); TEST_ASSERT_TRUE(wgReaches(c, ip("93.184.216.34"))); TEST_ASSERT_EQUAL_STRING("everything", describeWgRouting(c).c_str()); // A home network behind the server can't be reached without the full tunnel: said, not hidden. parseWgConf(conf("10.9.0.0/24, 192.168.1.0/24"), c); r = routingOf(c); TEST_ASSERT_EQUAL_INT(24, r.prefix); TEST_ASSERT_EQUAL_INT(1, r.unreachable); TEST_ASSERT_FALSE(wgReaches(c, ip("192.168.1.10"))); TEST_ASSERT_EQUAL_STRING("10.9.0.0/24, not 1 other range", describeWgRouting(c).c_str()); // The widest allowed range that holds this device's address is the tunnel's subnet. parseWgConf(conf("10.0.0.0/8"), c); TEST_ASSERT_EQUAL_INT(8, routingOf(c).prefix); TEST_ASSERT_TRUE(wgReaches(c, ip("10.200.3.4"))); TEST_ASSERT_EQUAL_INT(0, routingOf(c).unreachable); // Only the server itself allowed: the Address line's own subnet, and that one host outside it. parseWgConf(conf("172.16.5.1/32"), c); r = routingOf(c); TEST_ASSERT_EQUAL_INT(24, r.prefix); TEST_ASSERT_EQUAL_INT(1, r.unreachable); } int main() { UNITY_BEGIN(); RUN_TEST(test_a_usual_file); RUN_TEST(test_as_people_write_them); RUN_TEST(test_it_survives_being_stored); RUN_TEST(test_what_is_refused_and_why); RUN_TEST(test_keys); RUN_TEST(test_what_goes_through_it); return UNITY_END(); }