From c8ce78315dbc0ad5ab3b2ad625e7d037ca843c30 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 15 Nov 2022 13:11:11 -0400 Subject: [PATCH 01/15] Fix missing option names At some point between 0.9.9 and 0.9.10 we removed the printing of option names when a value doesn't have a default, but this means the config is littered with things like: # This option sets the greater foo value. with no actual option name printed out when there is no default. This fixes it by always printing the option name in such a case, just with an empty value, e.g.: # This option sets the greater foo value. #big-foo= --- llarp/config/definition.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/llarp/config/definition.cpp b/llarp/config/definition.cpp index aa199ebea..76177603c 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -152,10 +152,8 @@ namespace llarp const std::string& section, std::vector comments) { auto& sectionComments = m_sectionComments[section]; - for (size_t i = 0; i < comments.size(); ++i) - { - sectionComments.emplace_back(std::move(comments[i])); - } + for (auto& c : comments) + sectionComments.emplace_back(std::move(c)); } void @@ -198,13 +196,22 @@ namespace llarp if (useValues and def->getNumberFound() > 0) { for (const auto& val : def->valuesAsString()) - fmt::format_to(sect_append, "\n{}={}\n", name, val); + fmt::format_to(sect_append, "\n{}={}", name, val); + *sect_append = '\n'; } - else if (not(def->hidden and not has_comment)) + else if (not def->hidden) { - for (const auto& val : def->defaultValuesAsString()) - fmt::format_to(sect_append, "\n{}{}={}\n", def->required ? "" : "#", name, val); + if (auto defaults = def->defaultValuesAsString(); not defaults.empty()) + for (const auto& val : defaults) + fmt::format_to(sect_append, "\n{}{}={}", def->required ? "" : "#", name, val); + else + // We have no defaults so we append it as "#opt-name=" so that we show the option name, + // and make it simple to uncomment and edit to the desired value. + fmt::format_to(sect_append, "\n#{}=", name); + *sect_append = '\n'; } + else if (has_comment) + *sect_append = '\n'; }); if (sect_str.empty()) From 68bb74a95dc2d060b4a56b605b1161ff468aef50 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 15 Nov 2022 13:14:15 -0400 Subject: [PATCH 02/15] Make [lokid]:rpc setting required in SN mode When running as a service node we can't do anything without a lokid rpc URL, and we don't necessarily have a good default for it. This makes it required so that we fail with an appropriate error message (rather than connect timeouts) if it is not specified. --- llarp/config/config.cpp | 1 + test/config/test_llarp_config_values.cpp | 49 +++++++++++------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index aa02da5c2..67d92fa2f 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -1179,6 +1179,7 @@ namespace llarp "lokid", "rpc", RelayOnly, + Required, Comment{ "oxenmq control address for for communicating with oxend. Depends on oxend's", "lmq-local-control configuration option. By default this value should be", diff --git a/test/config/test_llarp_config_values.cpp b/test/config/test_llarp_config_values.cpp index 8276cc59c..f0e9ebe17 100644 --- a/test/config/test_llarp_config_values.cpp +++ b/test/config/test_llarp_config_values.cpp @@ -75,6 +75,8 @@ run_config_test(mocks::Network env, std::string_view ini_str) throw std::runtime_error{"non zero return"}; } +const std::string ini_minimal = "[lokid]\nrpc=ipc://dummy\n"; + TEST_CASE("service node bind section on valid network", "[config]") { std::unordered_multimap env{ @@ -92,15 +94,14 @@ TEST_CASE("service node bind section on valid network", "[config]") REQUIRE(not mock_net.IsBogon(*maybe_addr)); } - SECTION("empty config") + SECTION("minimal config") { - std::string_view ini_str = ""; - REQUIRE_NOTHROW(run_config_test(env, ini_str)); + REQUIRE_NOTHROW(run_config_test(env, ini_minimal)); } SECTION("explicit bind via ifname") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] mock0=443 )"; @@ -108,7 +109,7 @@ mock0=443 } SECTION("explicit bind via ip address") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] inbound=1.1.1.1:443 )"; @@ -116,7 +117,7 @@ inbound=1.1.1.1:443 } SECTION("explicit bind via ip address with old syntax") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] 1.1.1.1=443 )"; @@ -125,7 +126,7 @@ inbound=1.1.1.1:443 } SECTION("ip spoof fails") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=8.8.8.8 public-port=443 @@ -136,7 +137,7 @@ inbound=1.1.1.1:443 } SECTION("explicit bind via ifname but fails from non existing ifname") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] ligma0=443 )"; @@ -145,7 +146,7 @@ ligma0=443 SECTION("explicit bind via ifname but fails from using loopback") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] lo=443 )"; @@ -154,7 +155,7 @@ lo=443 SECTION("explicit bind via explicit loopback") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] inbound=127.0.0.1:443 )"; @@ -162,7 +163,7 @@ inbound=127.0.0.1:443 } SECTION("public ip provided but no bind section") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=1.1.1.1 public-port=443 @@ -171,7 +172,7 @@ public-port=443 } SECTION("public ip provided with ip in bind section") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=1.1.1.1 public-port=443 @@ -196,7 +197,7 @@ TEST_CASE("service node bind section on nat network", "[config]") SECTION("public ip provided via inbound directive") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=1.1.1.1 public-port=443 @@ -209,7 +210,7 @@ inbound=10.1.1.1:443 SECTION("public ip provided with bind via ifname") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=1.1.1.1 public-port=443 @@ -222,7 +223,7 @@ mock0=443 SECTION("public ip provided bind via wildcard ip") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=1.1.1.1 public-port=443 @@ -232,7 +233,6 @@ inbound=0.0.0.0:443 )"; REQUIRE_THROWS(run_config_test(env, ini_str)); } - } TEST_CASE("service node bind section with multiple public ip", "[config]") @@ -242,14 +242,9 @@ TEST_CASE("service node bind section with multiple public ip", "[config]") {"mock0", llarp::IPRange::FromIPv4(2, 1, 1, 1, 32)}, {"lo", llarp::IPRange::FromIPv4(127, 0, 0, 1, 8)}, }; - SECTION("empty config") - { - std::string_view ini_str = ""; - REQUIRE_NOTHROW(run_config_test(env, ini_str)); - } SECTION("with old style wildcard for inbound and no public ip, fails") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] 0.0.0.0=443 )"; @@ -257,7 +252,7 @@ TEST_CASE("service node bind section with multiple public ip", "[config]") } SECTION("with old style wildcard for outbound") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] *=1443 )"; @@ -265,7 +260,7 @@ TEST_CASE("service node bind section with multiple public ip", "[config]") } SECTION("with wildcard via inbound directive no public ip given, fails") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] inbound=0.0.0.0:443 )"; @@ -274,7 +269,7 @@ inbound=0.0.0.0:443 } SECTION("with wildcard via inbound directive primary public ip given") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=1.1.1.1 public-port=443 @@ -286,7 +281,7 @@ inbound=0.0.0.0:443 } SECTION("with wildcard via inbound directive secondary public ip given") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [router] public-ip=2.1.1.1 public-port=443 @@ -298,7 +293,7 @@ inbound=0.0.0.0:443 } SECTION("with bind via interface name") { - std::string_view ini_str = R"( + auto ini_str = ini_minimal + R"( [bind] mock0=443 )"; From f9db657f64c069f54397f52951e0ddf7ececcb67 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 15 Nov 2022 13:15:54 -0400 Subject: [PATCH 03/15] Make Default&Required or Required&Hidden compilation failures Default & Required makes no sense: if we have a default it makes no sense to make it required. The previous behaviour when this was specified was to force an (uncommented) value in the config with the value, but this was only used in the test suite. Required & Hidden makes no sense either: if it's required to be specified we definitely don't want to hide it from the generated config file. These are now compile-time failures. --- llarp/config/definition.hpp | 10 +++ test/config/test_llarp_config_definition.cpp | 11 ++-- test/config/test_llarp_config_output.cpp | 68 +++++++++++--------- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index 8f9225c28..6cacd3180 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -202,6 +202,16 @@ namespace llarp OptionDefinition(std::string section_, std::string name_, Options&&... opts) : OptionDefinitionBase(section_, name_, opts...) { + constexpr bool has_default = + ((config::is_default_array || config::is_default) || ...); + constexpr bool has_required = + (std::is_same_v, config::Required_t> || ...); + constexpr bool has_hidden = + (std::is_same_v, config::Hidden_t> || ...); + static_assert( + not(has_default and has_required), "Default{...} and Required are mutually exclusive"); + static_assert(not(has_hidden and has_required), "Hidden and Required are mutually exclusive"); + (extractDefault(std::forward(opts)), ...); (extractAcceptor(std::forward(opts)), ...); (extractComments(std::forward(opts)), ...); diff --git a/test/config/test_llarp_config_definition.cpp b/test/config/test_llarp_config_definition.cpp index e3edec29c..80005d58c 100644 --- a/test/config/test_llarp_config_definition.cpp +++ b/test/config/test_llarp_config_definition.cpp @@ -104,7 +104,7 @@ TEST_CASE("OptionDefinition acceptor throws test", "[config]") TEST_CASE("OptionDefinition tryAccept missing option test", "[config]") { int unset = -1; - llarp::OptionDefinition def("foo", "bar", Required, Default{1}, [&](int arg) { + llarp::OptionDefinition def("foo", "bar", Required, [&](int arg) { (void)arg; unset = 0; // should never be called }); @@ -170,7 +170,6 @@ TEST_CASE("ConfigDefinition required test", "[config]") config.defineOption(std::make_unique>( "router", "threads", - Default{1}, Required)); CHECK_THROWS(config.validateRequiredFields()); @@ -186,13 +185,13 @@ TEST_CASE("ConfigDefinition section test", "[config]") config.defineOption(std::make_unique>( "foo", "bar", - Required, - Default{1})); + Required + )); config.defineOption(std::make_unique>( "goo", "bar", - Required, - Default{1})); + Required + )); CHECK_THROWS(config.validateRequiredFields()); diff --git a/test/config/test_llarp_config_output.cpp b/test/config/test_llarp_config_output.cpp index 13eb64c72..5ba47bf21 100644 --- a/test/config/test_llarp_config_output.cpp +++ b/test/config/test_llarp_config_output.cpp @@ -8,37 +8,37 @@ TEST_CASE("ConfigDefinition simple generate test", "[config]") { llarp::ConfigDefinition config{true}; - config.defineOption("foo", "bar", Required, Default{1}); + config.defineOption("foo", "bar", Required); config.defineOption("foo", "baz", Default{2}); - config.defineOption(std::make_unique>( - "foo", "quux", Required, Default{"hello"})); + config.defineOption( + std::make_unique>("foo", "quux", Default{"hello"})); - config.defineOption("argle", "bar", RelayOnly, Required, Default{3}); + config.defineOption("argle", "bar", RelayOnly, Required); config.defineOption("argle", "baz", Default{4}); - config.defineOption("argle", "quux", Required, Default{"the quick brown fox"}); + config.defineOption("argle", "quux", Default{"the quick brown fox"}); - config.defineOption("not", "for-routers", ClientOnly, Required, Default{1}); + config.defineOption("not", "for-routers", ClientOnly, Default{1}); std::string output = config.generateINIConfig(); CHECK(output == R"raw([foo] -bar=1 +#bar= #baz=2 -quux=hello +#quux=hello [argle] -bar=3 +#bar= #baz=4 -quux=the quick brown fox +#quux=the quick brown fox )raw"); } @@ -46,16 +46,24 @@ TEST_CASE("ConfigDefinition useValue test", "[config]") { llarp::ConfigDefinition config{true}; - config.defineOption("foo", "bar", Required, Default{1}); + config.defineOption("foo", "bar", Default{1}); - constexpr auto expected = "[foo]\n\n\nbar=1\n"; + constexpr auto expected = R"raw([foo] + + +#bar=1 +)raw"; CHECK(config.generateINIConfig(false) == expected); CHECK(config.generateINIConfig(true) == expected); config.addConfigValue("foo", "bar", "2"); - constexpr auto expectedWhenValueProvided = "[foo]\n\n\nbar=2\n"; + constexpr auto expectedWhenValueProvided = R"raw([foo] + + +bar=2 +)raw"; CHECK(config.generateINIConfig(false) == expected); CHECK(config.generateINIConfig(true) == expectedWhenValueProvided); @@ -67,8 +75,7 @@ TEST_CASE("ConfigDefinition section comments test") config.addSectionComments("foo", {"test comment"}); config.addSectionComments("foo", {"test comment 2"}); - config.defineOption(std::make_unique>( - "foo", "bar", Required, Default{1})); + config.defineOption(std::make_unique>("foo", "bar", Default{1})); std::string output = config.generateINIConfig(); @@ -77,7 +84,7 @@ TEST_CASE("ConfigDefinition section comments test") # test comment 2 -bar=1 +#bar=1 )raw"); } @@ -87,18 +94,21 @@ TEST_CASE("ConfigDefinition option comments test") config.addOptionComments("foo", "bar", {"test comment 1"}); config.addOptionComments("foo", "bar", {"test comment 2"}); - config.defineOption("foo", "bar", Required, Default{1}); + config.defineOption("foo", "bar", Default{1}); - config.defineOption("foo", "far", Default{"abc"}, + config.defineOption( + "foo", + "far", + Default{"abc"}, Comment{ - "Fill in the missing values:", - "___defg", + "Fill in the missing values:", + "___defg", }); config.defineOption("client", "omg", ClientOnly, Default{1}, Comment{"hi"}); config.defineOption("relay", "ftw", RelayOnly, Default{1}, Comment{"bye"}); - // has comment, so still gets shown. + // has comment, but is hidden: we show only the comment but not the value/default. config.defineOption("foo", "old-bar", Hidden, Default{456}); config.addOptionComments("foo", "old-bar", {"old bar option"}); @@ -112,14 +122,13 @@ TEST_CASE("ConfigDefinition option comments test") # test comment 1 # test comment 2 -bar=1 +#bar=1 # Fill in the missing values: # ___defg #far=abc # old bar option -#old-bar=456 [relay] @@ -139,7 +148,7 @@ TEST_CASE("ConfigDefinition empty comments test") config.addOptionComments("foo", "bar", {"option comment"}); config.addOptionComments("foo", "bar", {""}); - config.defineOption("foo", "bar", Required, Default{1}); + config.defineOption("foo", "bar", Default{1}); std::string output = config.generateINIConfig(); @@ -150,7 +159,7 @@ TEST_CASE("ConfigDefinition empty comments test") # option comment # -bar=1 +#bar=1 )raw"); } @@ -161,10 +170,10 @@ TEST_CASE("ConfigDefinition multi option comments") config.addSectionComments("foo", {"foo section comment"}); config.addOptionComments("foo", "bar", {"foo bar option comment"}); - config.defineOption("foo", "bar", Required, Default{1}); + config.defineOption("foo", "bar", Default{1}); config.addOptionComments("foo", "baz", {"foo baz option comment"}); - config.defineOption("foo", "baz", Required, Default{1}); + config.defineOption("foo", "baz", Default{1}); std::string output = config.generateINIConfig(); @@ -173,10 +182,9 @@ TEST_CASE("ConfigDefinition multi option comments") # foo bar option comment -bar=1 +#bar=1 # foo baz option comment -baz=1 +#baz=1 )raw"); } - From c57d8ef09110d21f5dbf98a0f4bb19828eef122e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 18 Nov 2022 10:00:32 +1100 Subject: [PATCH 04/15] fix: allow GUI window height as low as 600 for small screens --- gui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui b/gui index 37f274e86..054c26ff6 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit 37f274e86fea7fe0fcc472727398d118a6917854 +Subproject commit 054c26ff6137c7366a51d0b5258da103846c55f3 From e8d13618655c2a3ebf4567219f70c6f9a811dd45 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Fri, 18 Nov 2022 16:00:12 -0400 Subject: [PATCH 05/15] Remove another obsolete bootstrap I found another defunct obsolete boostrap file on a few foundation service nodes; this adds it to the list. --- llarp/router_contact.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/llarp/router_contact.cpp b/llarp/router_contact.cpp index a0655f1c7..194a59b0b 100644 --- a/llarp/router_contact.cpp +++ b/llarp/router_contact.cpp @@ -534,7 +534,9 @@ namespace llarp } static constexpr std::array obsolete_bootstraps = { - "7a16ac0b85290bcf69b2f3b52456d7e989ac8913b4afbb980614e249a3723218"sv}; + "7a16ac0b85290bcf69b2f3b52456d7e989ac8913b4afbb980614e249a3723218"sv, + "e6b3a6fe5e32c379b64212c72232d65b0b88ddf9bbaed4997409d329f8519e0b"sv, + }; bool RouterContact::IsObsoleteBootstrap() const From a518e654c555838db16900de3434115fc42ea880 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 16:27:21 -0500 Subject: [PATCH 06/15] add much logging around dns and windivert --- llarp/dns/server.cpp | 41 +++++++++++++++++- llarp/win32/windivert.cpp | 91 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/llarp/dns/server.cpp b/llarp/dns/server.cpp index 4a0d22da4..a03f10cce 100644 --- a/llarp/dns/server.cpp +++ b/llarp/dns/server.cpp @@ -146,6 +146,7 @@ namespace llarp::dns static void Callback(void* data, int err, ub_result* _result) { + log::debug(logcat, "got dns response from libunbound"); // take ownership of ub_result std::unique_ptr result{_result}; // borrow query @@ -158,6 +159,8 @@ namespace llarp::dns return; } + log::trace(logcat, "queueing dns response from libunbound to userland"); + // rewrite response OwnedBuffer pkt{(const byte_t*)result->answer_packet, (size_t)result->answer_len}; llarp_buffer_t buf{pkt}; @@ -490,6 +493,11 @@ namespace llarp::dns // no questions, send fail if (query.questions.empty()) { + log::info( + logcat, + "dns from {} to {} has empty query questions, sending failure reply", + from, + to); tmp->Cancel(); return true; } @@ -499,6 +507,12 @@ namespace llarp::dns // dont process .loki or .snode if (q.HasTLD(".loki") or q.HasTLD(".snode")) { + log::warning( + logcat, + "dns from {} to {} is for .loki or .snode but got to the unbound resolver, sending " + "failure reply", + from, + to); tmp->Cancel(); return true; } @@ -506,6 +520,12 @@ namespace llarp::dns if (not m_ctx) { // we are down + log::debug( + logcat, + "dns from {} to {} got to the unbound resolver, but the resolver isn't set up, " + "sending failure reply", + from, + to); tmp->Cancel(); return true; } @@ -514,6 +534,12 @@ namespace llarp::dns if (not running) { // we are stopping the win32 thread + log::debug( + logcat, + "dns from {} to {} got to the unbound resolver, but the resolver isn't running, " + "sending failure reply", + from, + to); tmp->Cancel(); return true; } @@ -533,7 +559,10 @@ namespace llarp::dns tmp->Cancel(); } else + { + log::trace(logcat, "dns from {} to {} processing via libunbound", from, to); m_Pending.insert(std::move(tmp)); + } return true; } @@ -549,6 +578,12 @@ namespace llarp::dns { parent_ptr->call( [self = shared_from_this(), parent_ptr = std::move(parent_ptr), buf = replyBuf.copy()] { + log::trace( + logcat, + "forwarding dns response from libunbound to userland (resolverAddr: {}, " + "askerAddr: {})", + self->resolverAddr, + self->askerAddr); self->src->SendTo(self->askerAddr, self->resolverAddr, OwnedBuffer::copy_from(buf)); // remove query parent_ptr->RemovePending(self); @@ -742,10 +777,14 @@ namespace llarp::dns { if (auto res_ptr = resolver.lock()) { - log::debug( + log::trace( logcat, "check resolver {} for dns from {} to {}", res_ptr->ResolverName(), from, to); if (res_ptr->MaybeHookDNS(ptr, msg, to, from)) + { + log::trace( + logcat, "resolver {} handling dns from {} to {}", res_ptr->ResolverName(), from, to); return true; + } } } return false; diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 344718db1..4faeee3d0 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -55,6 +55,90 @@ namespace llarp::win32 WINDIVERT_ADDRESS addr; }; + void + log_windivert_addr(const WINDIVERT_ADDRESS& addr) + { + std::string layer_str{}; + std::string ifidx_str{}; + switch (addr.Layer) + { + case WINDIVERT_LAYER_NETWORK: + layer_str = "WINDIVERT_LAYER_NETWORK"; + ifidx_str = "Network: [IfIdx: {}, SubIfIdx: {}]"_format( + addr.Network.IfIdx, addr.Network.SubIfIdx); + break; + case WINDIVERT_LAYER_NETWORK_FORWARD: + layer_str = "WINDIVERT_LAYER_NETWORK_FORWARD"; + break; + case WINDIVERT_LAYER_FLOW: + layer_str = "WINDIVERT_LAYER_FLOW"; + break; + case WINDIVERT_LAYER_SOCKET: + layer_str = "WINDIVERT_LAYER_SOCKET"; + break; + case WINDIVERT_LAYER_REFLECT: + layer_str = "WINDIVERT_LAYER_REFLECT"; + break; + default: + layer_str = "unknown"; + } + + std::string event_str{}; + switch (addr.Event) + { + case WINDIVERT_EVENT_NETWORK_PACKET: + event_str = "WINDIVERT_EVENT_NETWORK_PACKET"; + break; + case WINDIVERT_EVENT_FLOW_ESTABLISHED: + event_str = "WINDIVERT_EVENT_FLOW_ESTABLISHED"; + break; + case WINDIVERT_EVENT_FLOW_DELETED: + event_str = "WINDIVERT_EVENT_FLOW_DELETED"; + break; + case WINDIVERT_EVENT_SOCKET_BIND: + event_str = "WINDIVERT_EVENT_SOCKET_BIND"; + break; + case WINDIVERT_EVENT_SOCKET_CONNECT: + event_str = "WINDIVERT_EVENT_SOCKET_CONNECT"; + break; + case WINDIVERT_EVENT_SOCKET_LISTEN: + event_str = "WINDIVERT_EVENT_SOCKET_LISTEN"; + break; + case WINDIVERT_EVENT_SOCKET_ACCEPT: + event_str = "WINDIVERT_EVENT_SOCKET_ACCEPT"; + break; + case WINDIVERT_EVENT_SOCKET_CLOSE: + event_str = "WINDIVERT_EVENT_SOCKET_CLOSE"; + break; + case WINDIVERT_EVENT_REFLECT_OPEN: + event_str = "WINDIVERT_EVENT_REFLECT_OPEN"; + break; + case WINDIVERT_EVENT_REFLECT_CLOSE: + event_str = "WINDIVERT_EVENT_REFLECT_CLOSE"; + break; + default: + event_str = "unknown"; + } + + log::trace( + logcat, + "Windivert WINDIVERT_ADDRESS -- Timestamp: {}, Layer: {}, Event: {}, Sniffed: {}, " + "Outbound: {}, Loopback: {}, Imposter: {}, IPv6: {}, IPChecksum: {}, TCPChecksum: {}, " + "UDPChecksum: {}, {}", + addr.Timestamp, + layer_str, + event_str, + addr.Sniffed ? "true" : "false", + addr.Outbound ? "true" : "false", + addr.Loopback ? "true" : "false", + addr.Impostor ? "true" : "false", + addr.IPv6 ? "true" : "false", + addr.IPChecksum ? "true" : "false", + addr.TCPChecksum ? "true" : "false", + addr.UDPChecksum ? "true" : "false", + ifidx_str); + } + class IO : public llarp::vpn::I_Packet_IO { std::function m_Wake; @@ -106,8 +190,11 @@ namespace llarp::win32 throw win32::error{ err, fmt::format("failed to receive packet from windivert (code={})", err)}; } - log::trace(logcat, "got packet of size {}B", sz); pkt.resize(sz); + + log::trace(logcat, "got packet of size {}B", sz); + log_windivert_addr(addr); + return Packet{std::move(pkt), std::move(addr)}; } @@ -117,6 +204,8 @@ namespace llarp::win32 const auto& pkt = w_pkt.pkt; const auto* addr = &w_pkt.addr; log::trace(logcat, "send dns packet of size {}B", pkt.size()); + log_windivert_addr(w_pkt.addr); + UINT sz{}; if (!wd::send(m_Handle, pkt.data(), pkt.size(), &sz, addr)) throw win32::error{"windivert send failed"}; From 133cee0fd9b3c95777701e397d4429d9e5287244 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 16:27:56 -0500 Subject: [PATCH 07/15] Remove obsolete/extraneous WouldLoop function The DNS resolver code should not and can not be responsible for preventing packet looping. --- llarp/dns/server.cpp | 17 ----------------- llarp/dns/server.hpp | 10 ---------- 2 files changed, 27 deletions(-) diff --git a/llarp/dns/server.cpp b/llarp/dns/server.cpp index a03f10cce..ef1c01c03 100644 --- a/llarp/dns/server.cpp +++ b/llarp/dns/server.cpp @@ -455,20 +455,6 @@ namespace llarp::dns Up(m_conf); } - bool - WouldLoop(const SockAddr& to, const SockAddr& from) const override - { -#if defined(ANDROID) - (void)to; - (void)from; - return false; -#else - const auto& vec = m_conf.m_upstreamDNS; - return std::find(vec.begin(), vec.end(), to) != std::end(vec) - or std::find(vec.begin(), vec.end(), from) != std::end(vec); -#endif - } - template void call(Callable&& f) @@ -486,9 +472,6 @@ namespace llarp::dns const SockAddr& to, const SockAddr& from) override { - if (WouldLoop(to, from)) - return false; - auto tmp = std::make_shared(weak_from_this(), query, source, to, from); // no questions, send fail if (query.questions.empty()) diff --git a/llarp/dns/server.hpp b/llarp/dns/server.hpp index 7f22df48e..6b1ba54b3 100644 --- a/llarp/dns/server.hpp +++ b/llarp/dns/server.hpp @@ -197,16 +197,6 @@ namespace llarp::dns const Message& query, const SockAddr& to, const SockAddr& from) = 0; - - /// Returns true if a packet with to and from addresses is something that would cause a - /// resolution loop and thus should not be used on this resolver - virtual bool - WouldLoop(const SockAddr& to, const SockAddr& from) const - { - (void)to; - (void)from; - return false; - } }; // Base class for DNS proxy From 5238c3f1a0d5101d6f443a22a746d411e0fe9cc1 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 16:30:39 -0500 Subject: [PATCH 08/15] force windivert to recalc IP checksum --- llarp/win32/windivert.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 4faeee3d0..6f8a3aa58 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -23,6 +23,7 @@ namespace llarp::win32 decltype(::WinDivertOpen)* open = nullptr; decltype(::WinDivertClose)* close = nullptr; decltype(::WinDivertShutdown)* shutdown = nullptr; + decltype(::WinDivertHelperCalcChecksums)* calc_checksum = nullptr; decltype(::WinDivertSend)* send = nullptr; decltype(::WinDivertRecv)* recv = nullptr; decltype(::WinDivertHelperFormatIPv4Address)* format_ip4 = nullptr; @@ -41,6 +42,7 @@ namespace llarp::win32 "WinDivertOpen", open, "WinDivertClose", close, "WinDivertShutdown", shutdown, + "WinDivertHelperCalcChecksums", calc_checksum, "WinDivertSend", send, "WinDivertRecv", recv, "WinDivertHelperFormatIPv4Address", format_ip4, @@ -199,14 +201,18 @@ namespace llarp::win32 } void - send_packet(const Packet& w_pkt) const + send_packet(Packet&& w_pkt) const { - const auto& pkt = w_pkt.pkt; - const auto* addr = &w_pkt.addr; + auto& pkt = w_pkt.pkt; + auto* addr = &w_pkt.addr; + log::trace(logcat, "send dns packet of size {}B", pkt.size()); log_windivert_addr(w_pkt.addr); UINT sz{}; + // recalc IP packet checksum in case it needs it + wd::calc_checksum(pkt.data(), pkt.size(), addr, 0); + if (!wd::send(m_Handle, pkt.data(), pkt.size(), &sz, addr)) throw win32::error{"windivert send failed"}; } From 548ce5c3a2a097a2279825decc0ed61656a3c116 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 16:32:15 -0500 Subject: [PATCH 09/15] invert packet direction on WINDIVERT_ADDRESS We simply keep the WINDIVERT_ADDRESS struct given on recv, so when using it for send we need to invert the direction (the Output bit) --- llarp/win32/windivert.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 6f8a3aa58..d2dadc01a 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -206,6 +206,8 @@ namespace llarp::win32 auto& pkt = w_pkt.pkt; auto* addr = &w_pkt.addr; + addr->Outbound = !addr->Outbound; // re-used from recv, so invert direction + log::trace(logcat, "send dns packet of size {}B", pkt.size()); log_windivert_addr(w_pkt.addr); From d44ad497fd10f56720979ad626cd9857820ed271 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 17:48:03 -0500 Subject: [PATCH 10/15] rvalue ref -> value --- llarp/win32/windivert.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index d2dadc01a..67f7273dc 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -201,7 +201,7 @@ namespace llarp::win32 } void - send_packet(Packet&& w_pkt) const + send_packet(Packet w_pkt) const { auto& pkt = w_pkt.pkt; auto* addr = &w_pkt.addr; From 3d71bbd1e435f9eb4daa3a3dfc59e2b9a7fb62ba Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 17:59:54 -0500 Subject: [PATCH 11/15] log func should return a string instead --- llarp/win32/windivert.cpp | 175 +++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 67f7273dc..157a4d881 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -12,6 +12,92 @@ extern "C" #include } +namespace +{ + std::string + windivert_addr_to_string(const WINDIVERT_ADDRESS& addr) + { + std::string layer_str{}; + std::string ifidx_str{}; + switch (addr.Layer) + { + case WINDIVERT_LAYER_NETWORK: + layer_str = "WINDIVERT_LAYER_NETWORK"; + ifidx_str = "Network: [IfIdx: {}, SubIfIdx: {}]"_format( + addr.Network.IfIdx, addr.Network.SubIfIdx); + break; + case WINDIVERT_LAYER_NETWORK_FORWARD: + layer_str = "WINDIVERT_LAYER_NETWORK_FORWARD"; + break; + case WINDIVERT_LAYER_FLOW: + layer_str = "WINDIVERT_LAYER_FLOW"; + break; + case WINDIVERT_LAYER_SOCKET: + layer_str = "WINDIVERT_LAYER_SOCKET"; + break; + case WINDIVERT_LAYER_REFLECT: + layer_str = "WINDIVERT_LAYER_REFLECT"; + break; + default: + layer_str = "unknown"; + } + + std::string event_str{}; + switch (addr.Event) + { + case WINDIVERT_EVENT_NETWORK_PACKET: + event_str = "WINDIVERT_EVENT_NETWORK_PACKET"; + break; + case WINDIVERT_EVENT_FLOW_ESTABLISHED: + event_str = "WINDIVERT_EVENT_FLOW_ESTABLISHED"; + break; + case WINDIVERT_EVENT_FLOW_DELETED: + event_str = "WINDIVERT_EVENT_FLOW_DELETED"; + break; + case WINDIVERT_EVENT_SOCKET_BIND: + event_str = "WINDIVERT_EVENT_SOCKET_BIND"; + break; + case WINDIVERT_EVENT_SOCKET_CONNECT: + event_str = "WINDIVERT_EVENT_SOCKET_CONNECT"; + break; + case WINDIVERT_EVENT_SOCKET_LISTEN: + event_str = "WINDIVERT_EVENT_SOCKET_LISTEN"; + break; + case WINDIVERT_EVENT_SOCKET_ACCEPT: + event_str = "WINDIVERT_EVENT_SOCKET_ACCEPT"; + break; + case WINDIVERT_EVENT_SOCKET_CLOSE: + event_str = "WINDIVERT_EVENT_SOCKET_CLOSE"; + break; + case WINDIVERT_EVENT_REFLECT_OPEN: + event_str = "WINDIVERT_EVENT_REFLECT_OPEN"; + break; + case WINDIVERT_EVENT_REFLECT_CLOSE: + event_str = "WINDIVERT_EVENT_REFLECT_CLOSE"; + break; + default: + event_str = "unknown"; + } + + return fmt::format( + "Windivert WINDIVERT_ADDRESS -- Timestamp: {}, Layer: {}, Event: {}, Sniffed: {}, " + "Outbound: {}, Loopback: {}, Imposter: {}, IPv6: {}, IPChecksum: {}, TCPChecksum: {}, " + "UDPChecksum: {}, {}", + addr.Timestamp, + layer_str, + event_str, + addr.Sniffed ? "true" : "false", + addr.Outbound ? "true" : "false", + addr.Loopback ? "true" : "false", + addr.Impostor ? "true" : "false", + addr.IPv6 ? "true" : "false", + addr.IPChecksum ? "true" : "false", + addr.TCPChecksum ? "true" : "false", + addr.UDPChecksum ? "true" : "false", + ifidx_str); + } +} + namespace llarp::win32 { static auto logcat = log::Cat("windivert"); @@ -57,90 +143,6 @@ namespace llarp::win32 WINDIVERT_ADDRESS addr; }; - void - log_windivert_addr(const WINDIVERT_ADDRESS& addr) - { - std::string layer_str{}; - std::string ifidx_str{}; - switch (addr.Layer) - { - case WINDIVERT_LAYER_NETWORK: - layer_str = "WINDIVERT_LAYER_NETWORK"; - ifidx_str = "Network: [IfIdx: {}, SubIfIdx: {}]"_format( - addr.Network.IfIdx, addr.Network.SubIfIdx); - break; - case WINDIVERT_LAYER_NETWORK_FORWARD: - layer_str = "WINDIVERT_LAYER_NETWORK_FORWARD"; - break; - case WINDIVERT_LAYER_FLOW: - layer_str = "WINDIVERT_LAYER_FLOW"; - break; - case WINDIVERT_LAYER_SOCKET: - layer_str = "WINDIVERT_LAYER_SOCKET"; - break; - case WINDIVERT_LAYER_REFLECT: - layer_str = "WINDIVERT_LAYER_REFLECT"; - break; - default: - layer_str = "unknown"; - } - - std::string event_str{}; - switch (addr.Event) - { - case WINDIVERT_EVENT_NETWORK_PACKET: - event_str = "WINDIVERT_EVENT_NETWORK_PACKET"; - break; - case WINDIVERT_EVENT_FLOW_ESTABLISHED: - event_str = "WINDIVERT_EVENT_FLOW_ESTABLISHED"; - break; - case WINDIVERT_EVENT_FLOW_DELETED: - event_str = "WINDIVERT_EVENT_FLOW_DELETED"; - break; - case WINDIVERT_EVENT_SOCKET_BIND: - event_str = "WINDIVERT_EVENT_SOCKET_BIND"; - break; - case WINDIVERT_EVENT_SOCKET_CONNECT: - event_str = "WINDIVERT_EVENT_SOCKET_CONNECT"; - break; - case WINDIVERT_EVENT_SOCKET_LISTEN: - event_str = "WINDIVERT_EVENT_SOCKET_LISTEN"; - break; - case WINDIVERT_EVENT_SOCKET_ACCEPT: - event_str = "WINDIVERT_EVENT_SOCKET_ACCEPT"; - break; - case WINDIVERT_EVENT_SOCKET_CLOSE: - event_str = "WINDIVERT_EVENT_SOCKET_CLOSE"; - break; - case WINDIVERT_EVENT_REFLECT_OPEN: - event_str = "WINDIVERT_EVENT_REFLECT_OPEN"; - break; - case WINDIVERT_EVENT_REFLECT_CLOSE: - event_str = "WINDIVERT_EVENT_REFLECT_CLOSE"; - break; - default: - event_str = "unknown"; - } - - log::trace( - logcat, - "Windivert WINDIVERT_ADDRESS -- Timestamp: {}, Layer: {}, Event: {}, Sniffed: {}, " - "Outbound: {}, Loopback: {}, Imposter: {}, IPv6: {}, IPChecksum: {}, TCPChecksum: {}, " - "UDPChecksum: {}, {}", - addr.Timestamp, - layer_str, - event_str, - addr.Sniffed ? "true" : "false", - addr.Outbound ? "true" : "false", - addr.Loopback ? "true" : "false", - addr.Impostor ? "true" : "false", - addr.IPv6 ? "true" : "false", - addr.IPChecksum ? "true" : "false", - addr.TCPChecksum ? "true" : "false", - addr.UDPChecksum ? "true" : "false", - ifidx_str); - } - class IO : public llarp::vpn::I_Packet_IO { std::function m_Wake; @@ -195,8 +197,7 @@ namespace llarp::win32 pkt.resize(sz); log::trace(logcat, "got packet of size {}B", sz); - log_windivert_addr(addr); - + log::trace(logcat, "{}", windivert_addr_to_string(addr)); return Packet{std::move(pkt), std::move(addr)}; } @@ -209,7 +210,7 @@ namespace llarp::win32 addr->Outbound = !addr->Outbound; // re-used from recv, so invert direction log::trace(logcat, "send dns packet of size {}B", pkt.size()); - log_windivert_addr(w_pkt.addr); + log::trace(logcat, "{}", windivert_addr_to_string(w_pkt.addr)); UINT sz{}; // recalc IP packet checksum in case it needs it From c4c81cc9f84988110a4dae7158d6e061b65bbe48 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 18:33:12 -0500 Subject: [PATCH 12/15] I hate clang-format sometimes --- llarp/win32/windivert.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 157a4d881..41d5d43a3 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -23,8 +23,8 @@ namespace { case WINDIVERT_LAYER_NETWORK: layer_str = "WINDIVERT_LAYER_NETWORK"; - ifidx_str = "Network: [IfIdx: {}, SubIfIdx: {}]"_format( - addr.Network.IfIdx, addr.Network.SubIfIdx); + ifidx_str = + "Network: [IfIdx: {}, SubIfIdx: {}]"_format(addr.Network.IfIdx, addr.Network.SubIfIdx); break; case WINDIVERT_LAYER_NETWORK_FORWARD: layer_str = "WINDIVERT_LAYER_NETWORK_FORWARD"; @@ -96,7 +96,7 @@ namespace addr.UDPChecksum ? "true" : "false", ifidx_str); } -} +} // namespace namespace llarp::win32 { From 1e294652375757cff9848f1e9b7d5cf14197bdbe Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 22 Nov 2022 18:39:22 -0500 Subject: [PATCH 13/15] fix missing namespace --- llarp/win32/windivert.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 41d5d43a3..48f5c27b5 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -14,6 +14,8 @@ extern "C" namespace { + using namespace oxen::log::literals; + std::string windivert_addr_to_string(const WINDIVERT_ADDRESS& addr) { From c3d212054accc34142d61c7b2f393ceb1e109899 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 29 Nov 2022 12:25:59 -0400 Subject: [PATCH 14/15] Update deps to latest versions --- cmake/StaticBuild.cmake | 12 ++++++------ external/oxen-encoding | 2 +- gui | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index ae4421bc9..78e77a370 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -11,12 +11,12 @@ set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz) set(OPENSSL_HASH SHA256=83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e CACHE STRING "openssl source hash") -set(EXPAT_VERSION 2.4.9 CACHE STRING "expat version") +set(EXPAT_VERSION 2.5.0 CACHE STRING "expat version") string(REPLACE "." "_" EXPAT_TAG "R_${EXPAT_VERSION}") set(EXPAT_MIRROR ${LOCAL_MIRROR} https://github.com/libexpat/libexpat/releases/download/${EXPAT_TAG} CACHE STRING "expat download mirror(s)") set(EXPAT_SOURCE expat-${EXPAT_VERSION}.tar.xz) -set(EXPAT_HASH SHA512=8508379b4915d84d50f3638678a90792179c98247d1cb5e6e6387d117af4dc148ac7031c1debea8b96e7b710ef436cf0dd5da91f3d22b8186a00cfafe1201169 +set(EXPAT_HASH SHA512=2da73b991b7c0c54440485c787e5edeb3567230204e31b3cac1c3a6713ec6f9f1554d3afffc0f8336168dfd5df02db4a69bcf21b4d959723d14162d13ab87516 CACHE STRING "expat source hash") set(UNBOUND_VERSION 1.17.0 CACHE STRING "unbound version") @@ -25,11 +25,11 @@ set(UNBOUND_SOURCE unbound-${UNBOUND_VERSION}.tar.gz) set(UNBOUND_HASH SHA512=f6b9f279330fb19b5feca09524959940aad8c4e064528aa82b369c726d77e9e8e5ca23f366f6e9edcf2c061b96f482ed7a2c26ac70fc15ae5762b3d7e36a5284 CACHE STRING "unbound source hash") -set(SQLITE3_VERSION 3390400 CACHE STRING "sqlite3 version") +set(SQLITE3_VERSION 3400000 CACHE STRING "sqlite3 version") set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2022 CACHE STRING "sqlite3 download mirror(s)") set(SQLITE3_SOURCE sqlite-autoconf-${SQLITE3_VERSION}.tar.gz) -set(SQLITE3_HASH SHA3_256=431328e30d12c551da9ba7ef2122b269076058512014afa799caaf62ca567090 +set(SQLITE3_HASH SHA3_256=7ee8f02b21edb4489df5082b5cf5b7ef47bcebcdb0e209bf14240db69633c878 CACHE STRING "sqlite3 source hash") set(SODIUM_VERSION 1.0.18 CACHE STRING "libsodium version") @@ -62,11 +62,11 @@ set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) set(ZLIB_HASH SHA256=d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98 CACHE STRING "zlib source hash") -set(CURL_VERSION 7.85.0 CACHE STRING "curl version") +set(CURL_VERSION 7.86.0 CACHE STRING "curl version") set(CURL_MIRROR ${LOCAL_MIRROR} https://curl.haxx.se/download https://curl.askapache.com CACHE STRING "curl mirror(s)") set(CURL_SOURCE curl-${CURL_VERSION}.tar.xz) -set(CURL_HASH SHA512=b57cc31649a4f47cc4b482f56a85c86c8e8aaeaf01bc1b51b065fdb9145a9092bc52535e52a85a66432eb163605b2edbf5bc5c33ea6e40e50f26a69ad1365cbd +set(CURL_HASH SHA512=18e03a3c00f22125e07bddb18becbf5acdca22baeb7b29f45ef189a5c56f95b2d51247813f7a9a90f04eb051739e9aa7d3a1c5be397bae75d763a2b918d1b656 CACHE STRING "curl source hash") include(ExternalProject) diff --git a/external/oxen-encoding b/external/oxen-encoding index 707a83609..a869ae2b0 160000 --- a/external/oxen-encoding +++ b/external/oxen-encoding @@ -1 +1 @@ -Subproject commit 707a83609fb64d09b61ed1e56c82bf692050d2a1 +Subproject commit a869ae2b0152ad70855e3774a425c39a25ae1ca6 diff --git a/gui b/gui index 054c26ff6..1545d5479 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit 054c26ff6137c7366a51d0b5258da103846c55f3 +Subproject commit 1545d5479eb0db2df96db08b52bf76503eabf0ee From d9d3041dcea863058902b7aaa6012a89e36ca9c3 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 29 Nov 2022 12:26:35 -0400 Subject: [PATCH 15/15] Bump version for fix release In retrospect the last release really should have been called 0.10.0 and this should be 0.10.1, but too late now. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5253b9572..77d85b073 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ endif() project(lokinet - VERSION 0.9.10 + VERSION 0.9.11 DESCRIPTION "lokinet - IP packet onion router" LANGUAGES ${LANGS})