diff --git a/.drone.jsonnet b/.drone.jsonnet index 93a8b82e1..54951b7b6 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -21,6 +21,7 @@ local debian_pipeline(name, image, werror=true, cmake_extra='', extra_cmds=[], + jobs=6, loki_repo=false, allow_fail=false) = { kind: 'pipeline', @@ -55,7 +56,7 @@ local debian_pipeline(name, image, (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + '-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') + cmake_extra, - 'ninja -v', + 'ninja -j' + jobs + ' -v', '../contrib/ci/drone-gdb.sh ./test/testAll --use-colour yes', ] + extra_cmds, } @@ -93,6 +94,7 @@ local windows_cross_pipeline(name, image, cmake_extra='', toolchain='32', extra_cmds=[], + jobs=6, allow_fail=false) = { kind: 'pipeline', type: 'docker', @@ -121,7 +123,7 @@ local windows_cross_pipeline(name, image, (if lto then '' else '-DWITH_LTO=OFF ') + "-DBUILD_STATIC_DEPS=ON -DDOWNLOAD_SODIUM=ON -DBUILD_PACKAGE=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DNATIVE_BUILD=OFF -DSTATIC_LINK=ON" + cmake_extra, - 'ninja -v package', + 'ninja -j' + jobs + ' -v package', ] + extra_cmds, } ], @@ -178,7 +180,13 @@ local deb_builder(image, distro, distro_branch, arch='amd64', loki_repo=true) = // Macos build -local mac_builder(name, build_type='Release', werror=true, cmake_extra='', extra_cmds=[], allow_fail=false) = { +local mac_builder(name, + build_type='Release', + werror=true, + cmake_extra='', + extra_cmds=[], + jobs=6, + allow_fail=false) = { kind: 'pipeline', type: 'exec', name: name, @@ -198,7 +206,7 @@ local mac_builder(name, build_type='Release', werror=true, cmake_extra='', extra 'cd build', 'cmake .. -G Ninja -DCMAKE_CXX_FLAGS=-fcolor-diagnostics -DCMAKE_BUILD_TYPE='+build_type+' ' + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + cmake_extra, - 'ninja -v', + 'ninja -j' + jobs + ' -v', './test/testAll --use-colour yes', ] + extra_cmds, } @@ -233,8 +241,8 @@ local mac_builder(name, build_type='Release', werror=true, cmake_extra='', extra cmake_extra='-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8', loki_repo=true), // ARM builds (ARM64 and armhf) - debian_pipeline("Debian sid (ARM64)", "debian:sid", arch="arm64"), - debian_pipeline("Debian buster (armhf)", "arm32v7/debian:buster", arch="arm64", cmake_extra='-DDOWNLOAD_SODIUM=ON'), + debian_pipeline("Debian sid (ARM64)", "debian:sid", arch="arm64", jobs=4), + debian_pipeline("Debian buster (armhf)", "arm32v7/debian:buster", arch="arm64", cmake_extra='-DDOWNLOAD_SODIUM=ON', jobs=4), // Static armhf build (gets uploaded) debian_pipeline("Static (buster armhf)", "arm32v7/debian:buster", arch="arm64", deps='g++ python3-dev automake libtool', cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + @@ -243,7 +251,8 @@ local mac_builder(name, build_type='Release', werror=true, cmake_extra='', extra extra_cmds=[ '../contrib/ci/drone-check-static-libs.sh', 'UPLOAD_OS=linux-armhf ../contrib/ci/drone-static-upload.sh' - ]), + ], + jobs=4), // android apk builder apk_builder("android apk", "registry.oxen.rocks/lokinet-ci-android", extra_cmds=['UPLOAD_OS=anrdoid ../contrib/ci/drone-static-upload.sh']), diff --git a/CMakeLists.txt b/CMakeLists.txt index 07b580d08..f400e0339 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,11 @@ if(CCACHE_PROGRAM) endif() project(lokinet - VERSION 0.9.0 + VERSION 0.9.1 DESCRIPTION "lokinet - IP packet onion router" LANGUAGES C CXX) -set(RELEASE_MOTTO "Proof of soon" CACHE STRING "Release motto") +set(RELEASE_MOTTO "A Series of Tubes" CACHE STRING "Release motto") add_definitions(-DLLARP_VERSION_MAJOR=${lokinet_VERSION_MAJOR}) add_definitions(-DLLARP_VERSION_MINOR=${lokinet_VERSION_MINOR}) diff --git a/android/src/network/loki/lokinet/LokinetDaemon.java b/android/src/network/loki/lokinet/LokinetDaemon.java index ed808d7a5..072d21ab4 100644 --- a/android/src/network/loki/lokinet/LokinetDaemon.java +++ b/android/src/network/loki/lokinet/LokinetDaemon.java @@ -114,11 +114,17 @@ public class LokinetDaemon extends VpnService builder.setMtu(1500); String[] parts = ourRange.split("/"); - String ourIP = parts[0]; + String ourIPv4 = parts[0]; int ourMask = Integer.parseInt(parts[1]); - builder.addAddress(ourIP, ourMask); + // set ip4 + builder.addAddress(ourIPv4, ourMask); builder.addRoute("0.0.0.0", 0); + // set ip6 + // TODO: convert ipv4 to fd00::/8 range for ipv6 + // builder.addAddress(ourIPv6, ourMask + 96); + // builder.addRoute("::", 0); + builder.addDnsServer(upstreamDNS); builder.setSession("Lokinet"); builder.setConfigureIntent(null); @@ -134,24 +140,10 @@ public class LokinetDaemon extends VpnService InjectVPNFD(); - if (!Configure(config)) - { - //TODO: close vpn FD if this fails, either on native side, or here if possible - Log.e(LOG_TAG, "failed to configure daemon"); - return START_NOT_STICKY; - } - - m_UDPSocket = GetUDPSocket(); - - if (m_UDPSocket <= 0) - { - Log.e(LOG_TAG, "failed to get proper UDP handle from daemon, aborting."); - return START_NOT_STICKY; - } - - protect(m_UDPSocket); - new Thread(() -> { + Configure(config); + m_UDPSocket = GetUDPSocket(); + protect(m_UDPSocket); Mainloop(); }).start(); diff --git a/cmake/win32_installer_deps.cmake b/cmake/win32_installer_deps.cmake index 72945fbcf..ab59ef540 100644 --- a/cmake/win32_installer_deps.cmake +++ b/cmake/win32_installer_deps.cmake @@ -25,14 +25,14 @@ execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/lokinet-g WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) -install(PROGRAMS ${TUNTAP_EXE} DESTINATION bin) -install(FILES ${BOOTSTRAP_FILE} DESTINATION share) +install(PROGRAMS ${TUNTAP_EXE} DESTINATION bin COMPONENT tuntap) +install(FILES ${BOOTSTRAP_FILE} DESTINATION share COMPONENT lokinet) set(CPACK_PACKAGE_INSTALL_DIRECTORY "Lokinet") set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/win32-setup/lokinet.ico") set(CPACK_NSIS_DEFINES "RequestExecutionLevel admin") set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) -set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '$INSTDIR\\\\bin\\\\tuntap-install.exe /S'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --install'\\nExecWait 'sc failure lokinet reset= 60 actions= restart/1000'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe -g C:\\\\ProgramData\\\\lokinet\\\\lokinet.ini'\\nCopyFiles '$INSTDIR\\\\share\\\\bootstrap.signed' C:\\\\ProgramData\\\\lokinet\\\\bootstrap.signed\\nExecWait '$INSTDIR\\\\bin\\\\lokinet-bootstrap.exe'") +set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ifFileExists $INSTDIR\\\\bin\\\\tuntap-install.exe 0 +2\\nExecWait '$INSTDIR\\\\bin\\\\tuntap-install.exe /S'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --install'\\nExecWait 'sc failure lokinet reset= 60 actions= restart/1000'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe -g C:\\\\ProgramData\\\\lokinet\\\\lokinet.ini'\\nCopyFiles '$INSTDIR\\\\share\\\\bootstrap.signed' C:\\\\ProgramData\\\\lokinet\\\\bootstrap.signed\\nExecWait '$INSTDIR\\\\bin\\\\lokinet-bootstrap.exe'") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'net stop lokinet'\\nExecWait 'taskkill /f /t /im lokinet-gui.exe'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --remove'\\nRMDir /r /REBOOTOK C:\\\\ProgramData\\\\lokinet") set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Lokinet.lnk' '$INSTDIR\\\\share\\\\gui\\\\lokinet-gui.exe'" @@ -40,3 +40,6 @@ set(CPACK_NSIS_CREATE_ICONS_EXTRA set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Lokinet.lnk'" ) + +get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) +list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") diff --git a/contrib/py/admin/lokinetmon b/contrib/py/admin/lokinetmon index 82e497000..8b3618dd7 100755 --- a/contrib/py/admin/lokinetmon +++ b/contrib/py/admin/lokinetmon @@ -7,6 +7,32 @@ import time import zmq +geo = None + +try: + import GeoIP + geo = GeoIP.open("/usr/share/GeoIP/GeoIP.dat", GeoIP.GEOIP_STANDARD) +except Exception as ex: + print('no geoip: {}'.format(ex)) + time.sleep(1) + + +def ip_to_flag(ip): + """ + convert an ip to a flag emoji + """ + # bail if no geoip available + if not geo: + return '' + # trim off excess ipv6 jizz + ip = ip.replace("::ffff:", "") + # get the country code + cc = geo.country_code_by_addr(ip) + # Unicode flag sequences are just country codes transposed into the REGIONAL + # INDICATOR SYMBOL LETTER A ... Z range (U+1F1E6 ... U+1F1FF): + flag = ''.join(chr(0x1f1e6 + ord(i) - ord('A')) for i in cc) + return '({}) {}'.format(cc, flag) + class Monitor: @@ -26,7 +52,7 @@ class Monitor: self._rpc_socket.connect(url) self._speed_samples = [(0,0,0,0)] * self._sample_size self._run = True - + def rpc(self, method): self._rpc_socket.send_multipart([method.encode(), b'lokinetmon'+method.encode()]) if not self._rpc_socket.poll(timeout=50): @@ -34,10 +60,10 @@ class Monitor: reply = self._rpc_socket.recv_multipart() if len(reply) >= 3 and reply[0:2] == [b'REPLY', b'lokinetmon'+method.encode()]: return reply[2].decode() - + def _close(self): self._rpc_socket.close(linger=0) - self._run = False + self._run = False curses.endwin() def update_data(self): @@ -62,7 +88,11 @@ class Monitor: y_pos += 1 self.win.addstr("me -> ") for hop in path["hops"]: - self.win.addstr(" {} ->".format(hop["router"][:4])) + hopstr = hop['router'][:4] + if 'ip' in hop: + hopstr += ' {}'.format(ip_to_flag(hop['ip'])) + self.win.addstr(" {} ->".format(hopstr)) + self.win.addstr(" [{} ms latency]".format(path["intro"]["latency"])) self.win.addstr(" [{} until expire]".format(self.time_to(path["expiresAt"]))) if path["expiresSoon"]: @@ -174,7 +204,7 @@ class Monitor: barstr = "#" * (samp - badsamp) pad = " " * (maxsamp - samp) return pad, barstr, '#' * badsamp - + def display_speedgraph(self, y_pos, maxsz=40): """ display global speed graph """ txmax, rxmax = 1024, 1024 @@ -260,9 +290,13 @@ class Monitor: self.win.move(y_pos, 1) self.txrate += sess["txRateCurrent"] self.rxrate += sess["rxRateCurrent"] + addr = sess['remoteAddr'] + if geo: + ip = addr.split(':')[0] + addr += '\t{}'.format(ip_to_flag(ip)) self.win.addstr( "{}\t[{}\ttx]\t[{}\trx]".format( - sess["remoteAddr"], self.speed_of(sess["txRateCurrent"]), self.speed_of(sess["rxRateCurrent"]) + addr, self.speed_of(sess["txRateCurrent"]), self.speed_of(sess["rxRateCurrent"]) ) ) if (sess['txMsgQueueSize'] or 0) > 1: @@ -333,7 +367,7 @@ class Monitor: self.version = json.loads(self.rpc("llarp.version"))['result']['version'] except: self.version = None - + while self._run: if self.update_data(): self.win.box() diff --git a/contrib/systemd-resolved/README.md b/contrib/systemd-resolved/README.md index e54f02f18..5cd18d98e 100644 --- a/contrib/systemd-resolved/README.md +++ b/contrib/systemd-resolved/README.md @@ -1,6 +1,20 @@ -To be put at `/usr/lib/systemd/resolved.conf.d/lokinet.conf` for distro use and `/etc/systemd/resolved.conf.d/lokinet.conf` for local admin use. +Lokinet now talks to systemd directly via sdbus to set up DNS, but in order for this to work the +user running lokinet (assumed `_lokinet` in these example files) needs permission to set dns servers +and domains. -To make use of it: +To set up the permissions: + +- If lokinet is running as some user other than `_lokinet` the change the `_lokinet` username inside + `lokinet.rules` and `lokinet.pkla`. + +- If on a Debian or Debian-derived distribution (such as Ubuntu) using polkit 105, + copy `lokinet.pkla` to `/var/lib/polkit-1/localauthority/10-vendor.d/lokinet.pkla` (for a distro + install) or `/etc/polkit-1/localauthority.conf.d/` (for a local install). + +- Copy `lokinet.rules` to `/usr/share/polkit-1/rules.d/` (distro install) or `/etc/polkit-1/rules.d` + (local install). + +Make use of it by switching to systemd-resolved: ``` sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf sudo systemctl enable --now systemd-resolved diff --git a/contrib/systemd-resolved/lokinet.conf b/contrib/systemd-resolved/lokinet.conf deleted file mode 100644 index 007db4fec..000000000 --- a/contrib/systemd-resolved/lokinet.conf +++ /dev/null @@ -1,3 +0,0 @@ -[Resolve] -DNS=127.3.2.1 -Domains=~loki ~snode diff --git a/contrib/systemd-resolved/lokinet.pkla b/contrib/systemd-resolved/lokinet.pkla new file mode 100644 index 000000000..ec7ad7098 --- /dev/null +++ b/contrib/systemd-resolved/lokinet.pkla @@ -0,0 +1,4 @@ +[Allow lokinet to set DNS settings] +Identity=unix-user:_lokinet +Action=org.freedesktop.resolve1.set-dns-servers;org.freedesktop.resolve1.set-domains +ResultAny=yes diff --git a/contrib/systemd-resolved/lokinet.rules b/contrib/systemd-resolved/lokinet.rules new file mode 100644 index 000000000..60bbfb3e3 --- /dev/null +++ b/contrib/systemd-resolved/lokinet.rules @@ -0,0 +1,9 @@ +/* Allow lokinet to set DNS settings */ +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.resolve1.set-dns-servers" || + action.id == "org.freedesktop.resolve1.set-domains") && + subject.user == "_lokinet") { + return polkit.Result.YES; + } +}); + diff --git a/contrib/windows.sh b/contrib/windows.sh index e89a67618..99c814590 100755 --- a/contrib/windows.sh +++ b/contrib/windows.sh @@ -12,6 +12,7 @@ cmake \ -DBUILD_PACKAGE=ON \ -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TESTING=OFF \ + -DBUILD_LIBLOKINET=ON \ -DWITH_TESTS=OFF \ -DNATIVE_BUILD=OFF \ -DSTATIC_LINK=ON \ diff --git a/docs/proto_v0.txt b/docs/proto_v0.txt index b949de74e..6a3bb0ecf 100644 --- a/docs/proto_v0.txt +++ b/docs/proto_v0.txt @@ -24,11 +24,11 @@ BE(x) is bittorrent encode x BD(x) is bittorrent decode x { a: b, y: z } is a dictionary with two keys a and y - who's values are b and z respectively + whose values are b and z respectively [ a, b, c ... ] is a list containing a b c and more items in that order -"" is a bytestring who's contents and length is described by the +"" is a bytestring whose contents and length is described by the quoted value "" * N is a bytestring containing the concatenated N times. @@ -354,8 +354,8 @@ hop length. link relay commit record (LRCR) record requesting relaying messages for 600 seconds to router -on network who's i is equal to RC.k and decrypt data any messages using -PKE(n, rc.p, c) as symettric key for encryption and decryption. +on network whose i is equal to RC.k and decrypt data any messages using +PKE(n, rc.p, c) as symmetric key for encryption and decryption. if l is provided and is less than 600 and greater than 10 then that lifespan is used (in seconds) instead of 600 seconds. @@ -845,8 +845,8 @@ X is parsed as a list of IP packet buffers. for each ip packet the source addresss is extracted and sent on the appropriate network interface. -When we recieve an ip packet from the internet to an exit address, we put it -into a TITM, and send it downstream the corrisponding path in an LRDM. +When we receive an ip packet from the internet to an exit address, we put it +into a TITM, and send it downstream the corresponding path in an LRDM. update exit path message (UXPM) diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 279b621a4..bc6423f85 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -186,6 +186,7 @@ add_library(liblokinet router/rc_gossiper.cpp router/router.cpp router/route_poker.cpp + router/systemd_resolved.cpp routing/dht_message.cpp routing/message_parser.cpp routing/path_confirm_message.cpp @@ -248,12 +249,16 @@ if(BUILD_LIBLOKINET) include(GNUInstallDirs) add_library(lokinet-shared SHARED lokinet_shared.cpp) target_link_libraries(lokinet-shared PUBLIC liblokinet) - install(TARGETS lokinet-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(WIN32) set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "") - target_link_libraries(lokinet-shared PUBLIC ws2_32 iphlpapi -fstack-protector) endif() set_target_properties(lokinet-shared PROPERTIES OUTPUT_NAME lokinet) + if(WIN32) + target_link_libraries(lokinet-shared PUBLIC ws2_32 iphlpapi -fstack-protector) + install(TARGETS lokinet-shared DESTINATION bin COMPONENT liblokinet) + else() + install(TARGETS lokinet-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT liblokinet) + endif() add_log_tag(lokinet-shared) endif() diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index e6deec451..4ef7e20b6 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -659,6 +659,21 @@ namespace llarp m_SRVRecords.push_back(std::move(newSRV)); }); + conf.defineOption( + "network", + "path-alignment-timeout", + ClientOnly, + Comment{ + "time in seconds how long to wait for a path to align to pivot routers", + "if not provided a sensible default will be used", + }, + [this](int val) { + if (val <= 0) + throw std::invalid_argument{ + "invalid path alignment timeout: " + std::to_string(val) + " <= 0"}; + m_PathAlignmentTimeout = std::chrono::seconds{val}; + }); + // Deprecated options: conf.defineOption("network", "enabled", Deprecated); } @@ -989,7 +1004,7 @@ namespace llarp constexpr Default DefaultLogType{"file"}; constexpr Default DefaultLogFile{""}; - constexpr Default DefaultLogLevel{"info"}; + constexpr Default DefaultLogLevel{"warn"}; conf.defineOption( "logging", diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 399fac8a2..7fb212df4 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -125,6 +125,8 @@ namespace llarp std::set m_OwnedRanges; std::optional m_TrafficPolicy; + std::optional m_PathAlignmentTimeout; + // TODO: // on-up // on-down diff --git a/llarp/context.cpp b/llarp/context.cpp index c62d5893d..9bf260ce7 100644 --- a/llarp/context.cpp +++ b/llarp/context.cpp @@ -27,7 +27,7 @@ namespace llarp { if (!loop) return false; - loop->call(std::move(f)); + loop->call_soon(std::move(f)); return true; } diff --git a/llarp/ev/ev_libuv.cpp b/llarp/ev/ev_libuv.cpp index 13e63c180..9832082d8 100644 --- a/llarp/ev/ev_libuv.cpp +++ b/llarp/ev/ev_libuv.cpp @@ -368,7 +368,10 @@ namespace llarp::uv bool Loop::inEventLoop() const { - return m_EventLoopThreadID and *m_EventLoopThreadID == std::this_thread::get_id(); + if (m_EventLoopThreadID) + return *m_EventLoopThreadID == std::this_thread::get_id(); + // assume we are in it because we haven't started up yet + return true; } } // namespace llarp::uv diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index ba7a1ac7f..0504f74d2 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -166,6 +167,13 @@ namespace llarp m_BaseV6Address = conf.m_baseV6Address; + if (conf.m_PathAlignmentTimeout) + { + m_PathAlignmentTimeout = *conf.m_PathAlignmentTimeout; + } + else + m_PathAlignmentTimeout = service::Endpoint::PathAlignmentTimeout(); + for (const auto& item : conf.m_mapAddrs) { if (not MapAddress(item.second, item.first, false)) @@ -261,24 +269,24 @@ namespace llarp bool TunEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function reply) { - auto ReplyToSNodeDNSWhenReady = [self = this, reply = reply]( - RouterID snode, auto msg, bool isV6) -> bool { - return self->EnsurePathToSNode( + auto ReplyToSNodeDNSWhenReady = [this, reply](RouterID snode, auto msg, bool isV6) -> bool { + return EnsurePathToSNode( snode, - [=](const RouterID&, exit::BaseSession_ptr s, [[maybe_unused]] service::ConvoTag tag) { - self->SendDNSReply(snode, s, msg, reply, isV6); + [this, snode, msg, reply, isV6]( + const RouterID&, exit::BaseSession_ptr s, [[maybe_unused]] service::ConvoTag tag) { + SendDNSReply(snode, s, msg, reply, isV6); }); }; - auto ReplyToLokiDNSWhenReady = [self = this, reply = reply]( + auto ReplyToLokiDNSWhenReady = [this, reply, timeout = PathAlignmentTimeout()]( service::Address addr, auto msg, bool isV6) -> bool { using service::Address; using service::OutboundContext; - return self->EnsurePathToService( + return EnsurePathToService( addr, - [=](const Address&, OutboundContext* ctx) { - self->SendDNSReply(addr, ctx, msg, reply, isV6); + [this, addr, msg, reply, isV6](const Address&, OutboundContext* ctx) { + SendDNSReply(addr, ctx, msg, reply, isV6); }, - 2s); + timeout); }; auto ReplyToDNSWhenReady = [ReplyToLokiDNSWhenReady, ReplyToSNodeDNSWhenReady]( @@ -295,14 +303,14 @@ namespace llarp } }; - auto ReplyToLokiSRVWhenReady = [self = this, reply = reply]( + auto ReplyToLokiSRVWhenReady = [this, reply, timeout = PathAlignmentTimeout()]( service::Address addr, auto msg) -> bool { using service::Address; using service::OutboundContext; - return self->EnsurePathToService( + return EnsurePathToService( addr, - [=](const Address&, OutboundContext* ctx) { + [msg, addr, reply](const Address&, OutboundContext* ctx) { if (ctx == nullptr) return; @@ -310,7 +318,7 @@ namespace llarp msg->AddSRVReply(introset.GetMatchingSRVRecords(addr.subdomain)); reply(*msg); }, - 2s); + timeout); }; if (msg.answers.size() > 0) @@ -784,6 +792,12 @@ namespace llarp Router()->loop()->add_ticker([this] { Flush(); }); + // Attempt to register DNS on the interface + systemd_resolved_set_dns( + m_IfName, + m_LocalResolverAddr.createSockAddr(), + false /* just .loki/.snode DNS initially */); + if (m_OnUp) { m_OnUp->NotifyAsync(NotifyParams()); @@ -916,7 +930,7 @@ namespace llarp } self->SendToOrQueue(addr, pkt.ConstBuffer(), service::ProtocolType::Exit); }, - 1s); + PathAlignmentTimeout()); return; } bool rewriteAddrs = true; diff --git a/llarp/handlers/tun.hpp b/llarp/handlers/tun.hpp index 77054fb6f..685898e17 100644 --- a/llarp/handlers/tun.hpp +++ b/llarp/handlers/tun.hpp @@ -135,6 +135,12 @@ namespace llarp return m_OwnedRanges; } + llarp_time_t + PathAlignmentTimeout() const override + { + return m_PathAlignmentTimeout; + } + /// ip packet against any exit policies we have /// returns false if this traffic is disallowed by any of those policies /// returns true otherwise @@ -222,7 +228,7 @@ namespace llarp std::function reply, bool sendIPv6) { - if (ctx) + if (ctx or HasAddress(addr)) { huint128_t ip = ObtainIPForAddr(addr); query->answers.clear(); @@ -267,6 +273,8 @@ namespace llarp std::optional m_TrafficPolicy; /// ranges we advetise as reachable std::set m_OwnedRanges; + /// how long to wait for path alignment + llarp_time_t m_PathAlignmentTimeout; }; } // namespace handlers diff --git a/llarp/net/net.cpp b/llarp/net/net.cpp index 48c886fef..05479d8dd 100644 --- a/llarp/net/net.cpp +++ b/llarp/net/net.cpp @@ -600,6 +600,40 @@ namespace llarp return false; } +#if !defined(TESTNET) + static constexpr std::array bogonRanges_v6 = { + // zero + IPRange{huint128_t{0}, netmask_ipv6_bits(128)}, + // loopback + IPRange{huint128_t{1}, netmask_ipv6_bits(128)}, + // yggdrasil + IPRange{huint128_t{uint128_t{0x0200'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(7)}, + // multicast + IPRange{huint128_t{uint128_t{0xff00'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(8)}, + // local + IPRange{huint128_t{uint128_t{0xfc00'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(8)}, + // local + IPRange{huint128_t{uint128_t{0xf800'0000'0000'0000UL, 0UL}}, netmask_ipv6_bits(8)}}; + + static constexpr std::array bogonRanges_v4 = { + IPRange::FromIPv4(0, 0, 0, 0, 8), + IPRange::FromIPv4(10, 0, 0, 0, 8), + IPRange::FromIPv4(100, 64, 0, 0, 10), + IPRange::FromIPv4(127, 0, 0, 0, 8), + IPRange::FromIPv4(169, 254, 0, 0, 16), + IPRange::FromIPv4(172, 16, 0, 0, 12), + IPRange::FromIPv4(192, 0, 0, 0, 24), + IPRange::FromIPv4(192, 0, 2, 0, 24), + IPRange::FromIPv4(192, 88, 99, 0, 24), + IPRange::FromIPv4(192, 168, 0, 0, 16), + IPRange::FromIPv4(198, 18, 0, 0, 15), + IPRange::FromIPv4(198, 51, 100, 0, 24), + IPRange::FromIPv4(203, 0, 113, 0, 24), + IPRange::FromIPv4(224, 0, 0, 0, 4), + IPRange::FromIPv4(240, 0, 0, 0, 4)}; + +#endif + bool IsBogon(const in6_addr& addr) { @@ -607,11 +641,14 @@ namespace llarp (void)addr; return false; #else - if (!ipv6_is_mapped_ipv4(addr)) + if (not ipv6_is_mapped_ipv4(addr)) { - static in6_addr zero = {}; - if (addr == zero) - return true; + const auto ip = net::In6ToHUInt(addr); + for (const auto& range : bogonRanges_v6) + { + if (range.Contains(ip)) + return true; + } return false; } return IsIPv4Bogon( @@ -636,28 +673,10 @@ namespace llarp } #if !defined(TESTNET) - static constexpr std::array bogonRanges = { - IPRange::FromIPv4(0, 0, 0, 0, 8), - IPRange::FromIPv4(10, 0, 0, 0, 8), - IPRange::FromIPv4(21, 0, 0, 0, 8), - IPRange::FromIPv4(100, 64, 0, 0, 10), - IPRange::FromIPv4(127, 0, 0, 0, 8), - IPRange::FromIPv4(169, 254, 0, 0, 16), - IPRange::FromIPv4(172, 16, 0, 0, 12), - IPRange::FromIPv4(192, 0, 0, 0, 24), - IPRange::FromIPv4(192, 0, 2, 0, 24), - IPRange::FromIPv4(192, 88, 99, 0, 24), - IPRange::FromIPv4(192, 168, 0, 0, 16), - IPRange::FromIPv4(198, 18, 0, 0, 15), - IPRange::FromIPv4(198, 51, 100, 0, 24), - IPRange::FromIPv4(203, 0, 113, 0, 24), - IPRange::FromIPv4(224, 0, 0, 0, 4), - IPRange::FromIPv4(240, 0, 0, 0, 4)}; - bool IsIPv4Bogon(const huint32_t& addr) { - for (const auto& bogon : bogonRanges) + for (const auto& bogon : bogonRanges_v4) { if (bogon.Contains(addr)) { diff --git a/llarp/net/route.cpp b/llarp/net/route.cpp index 840f15dda..051d86fae 100644 --- a/llarp/net/route.cpp +++ b/llarp/net/route.cpp @@ -498,6 +498,8 @@ namespace llarp::net { std::vector gateways; #ifdef __linux__ +#ifdef ANDROID +#else std::ifstream inf("/proc/net/route"); for (std::string line; std::getline(inf, line);) { @@ -513,7 +515,7 @@ namespace llarp::net } } } - +#endif return gateways; #elif _WIN32 ForEachWIN32Interface([&](auto w32interface) { diff --git a/llarp/net/sock_addr.cpp b/llarp/net/sock_addr.cpp index 3324493b3..85098d2ea 100644 --- a/llarp/net/sock_addr.cpp +++ b/llarp/net/sock_addr.cpp @@ -342,6 +342,17 @@ namespace llarp return {m_addr4.sin_addr.s_addr}; } + nuint128_t + SockAddr::getIPv6() const + { + nuint128_t a; + // Explicit cast to void* here to avoid non-trivial type copying warnings (technically this + // isn't trivial because of the zeroing default constructor, but it's trivial enough that this + // copy is safe). + std::memcpy(static_cast(&a), &m_addr.sin6_addr, 16); + return a; + } + void SockAddr::setIPv4(nuint32_t ip) { diff --git a/llarp/path/path.cpp b/llarp/path/path.cpp index 79ee51c68..fcc27347b 100644 --- a/llarp/path/path.cpp +++ b/llarp/path/path.cpp @@ -303,7 +303,9 @@ namespace llarp util::StatusObject PathHopConfig::ExtractStatus() const { + const auto ip = net::In6ToHUInt(rc.addrs[0].ip); util::StatusObject obj{ + {"ip", ip.ToString()}, {"lifetime", to_json(lifetime)}, {"router", rc.pubkey.ToHex()}, {"txid", txID.ToHex()}, @@ -410,9 +412,6 @@ namespace llarp auto dlt = now - m_LastLatencyTestTime; if (dlt > path::latency_interval && m_LastLatencyTestID == 0) { - // bail doing test if we are active - if (now - m_LastRecvMessage < path::latency_interval) - return; routing::PathLatencyMessage latency; latency.T = randint(); m_LastLatencyTestID = latency.T; @@ -497,6 +496,9 @@ namespace llarp } } + /// how long we wait for a path to become active again after it times out + constexpr auto PathReanimationTimeout = 45s; + bool Path::Expired(llarp_time_t now) const { @@ -504,7 +506,11 @@ namespace llarp return true; if (_status == ePathBuilding) return false; - if (_status == ePathEstablished || _status == ePathTimeout) + if (_status == ePathTimeout) + { + return now >= m_LastRecvMessage + PathReanimationTimeout; + } + if (_status == ePathEstablished) { return now >= ExpireTime(); } diff --git a/llarp/path/path_context.cpp b/llarp/path/path_context.cpp index accecefdd..fe737396e 100644 --- a/llarp/path/path_context.cpp +++ b/llarp/path/path_context.cpp @@ -302,7 +302,7 @@ namespace llarp { if (itr->second->Expired(now)) { - m_Router->outboundMessageHandler().QueueRemoveEmptyPath(itr->first); + m_Router->outboundMessageHandler().RemovePath(itr->first); itr = map.erase(itr); } else diff --git a/llarp/path/pathbuilder.cpp b/llarp/path/pathbuilder.cpp index 3fdb4d9ba..e035bbd5e 100644 --- a/llarp/path/pathbuilder.cpp +++ b/llarp/path/pathbuilder.cpp @@ -96,7 +96,10 @@ namespace llarp { // farthest hop // TODO: encrypt junk frames because our public keys are not eligator - loop->call([self = shared_from_this()] { self->result(self); }); + loop->call([self = shared_from_this()] { + self->result(self); + self->result = nullptr; + }); } else { @@ -125,47 +128,56 @@ namespace llarp static void PathBuilderKeysGenerated(std::shared_ptr ctx) { - if (!ctx->pathset->IsStopped()) - { - ctx->router->NotifyRouterEvent(ctx->router->pubkey(), ctx->path); + if (ctx->pathset->IsStopped()) + return; - const RouterID remote = ctx->path->Upstream(); - auto sentHandler = [ctx](auto status) { - if (status == SendStatus::Success) - { - ctx->router->pathContext().AddOwnPath(ctx->pathset, ctx->path); - ctx->pathset->PathBuildStarted(std::move(ctx->path)); - } - else - { - LogError(ctx->pathset->Name(), " failed to send LRCM to ", ctx->path->Upstream()); - ctx->path->EnterState(path::ePathFailed, ctx->router->Now()); - } - ctx->path = nullptr; - ctx->pathset = nullptr; - }; - if (ctx->router->SendToOrQueue(remote, ctx->LRCM, sentHandler)) - { - // persist session with router until this path is done - if (ctx->path) - ctx->router->PersistSessionUntil(remote, ctx->path->ExpireTime()); - } - else + ctx->router->NotifyRouterEvent(ctx->router->pubkey(), ctx->path); + + ctx->router->pathContext().AddOwnPath(ctx->pathset, ctx->path); + ctx->pathset->PathBuildStarted(ctx->path); + + const RouterID remote = ctx->path->Upstream(); + auto sentHandler = [router = ctx->router, path = ctx->path](auto status) { + if (status != SendStatus::Success) { - LogError(ctx->pathset->Name(), " failed to queue LRCM to ", remote); - sentHandler(SendStatus::NoLink); + path->EnterState(path::ePathFailed, router->Now()); } + }; + if (ctx->router->SendToOrQueue(remote, ctx->LRCM, sentHandler)) + { + // persist session with router until this path is done + if (ctx->path) + ctx->router->PersistSessionUntil(remote, ctx->path->ExpireTime()); + } + else + { + LogError(ctx->pathset->Name(), " failed to queue LRCM to ", remote); + sentHandler(SendStatus::NoLink); } } namespace path { + bool + BuildLimiter::Attempt(const RouterID& router) + { + return m_EdgeLimiter.Insert(router); + } + + void + BuildLimiter::Decay(llarp_time_t now) + { + m_EdgeLimiter.Decay(now); + } + + bool + BuildLimiter::Limited(const RouterID& router) const + { + return m_EdgeLimiter.Contains(router); + } + Builder::Builder(AbstractRouter* p_router, size_t pathNum, size_t hops) - : path::PathSet{pathNum} - , m_EdgeLimiter{MIN_PATH_BUILD_INTERVAL} - , _run{true} - , m_router{p_router} - , numHops{hops} + : path::PathSet{pathNum}, _run{true}, m_router{p_router}, numHops{hops} { CryptoManager::instance()->encryption_keygen(enckey); } @@ -180,7 +192,6 @@ namespace llarp void Builder::Tick(llarp_time_t) { const auto now = llarp::time_now_ms(); - m_EdgeLimiter.Decay(now); ExpirePaths(now, m_router); if (ShouldBuildMore(now)) BuildOne(); @@ -226,7 +237,7 @@ namespace llarp if (exclude.count(rc.pubkey)) return; - if (m_EdgeLimiter.Contains(rc.pubkey)) + if (BuildCooldownHit(rc.pubkey)) return; found = rc; @@ -253,6 +264,14 @@ namespace llarp Builder::Stop() { _run = false; + // tell all our paths that they have expired + const auto now = Now(); + for (auto& item : m_Paths) + { + item.second->EnterState(ePathExpired, now); + } + // remove expired paths + ExpirePaths(now, m_router); return true; } @@ -277,7 +296,7 @@ namespace llarp bool Builder::BuildCooldownHit(RouterID edge) const { - return m_EdgeLimiter.Contains(edge); + return m_router->pathBuildLimiter().Limited(edge); } bool @@ -399,7 +418,7 @@ namespace llarp return; lastBuild = Now(); const RouterID edge{hops[0].pubkey}; - if (not m_EdgeLimiter.Insert(edge)) + if (not m_router->pathBuildLimiter().Attempt(edge)) { LogWarn(Name(), " building too fast to edge router ", edge); return; @@ -437,8 +456,6 @@ namespace llarp { PathSet::HandlePathBuildFailedAt(p, edge); DoPathBuildBackoff(); - /// add it to the edge limter even if it's not an edge for simplicity - m_EdgeLimiter.Insert(edge); } void diff --git a/llarp/path/pathbuilder.hpp b/llarp/path/pathbuilder.hpp index fa1c1d367..48e72ae77 100644 --- a/llarp/path/pathbuilder.hpp +++ b/llarp/path/pathbuilder.hpp @@ -15,11 +15,31 @@ namespace llarp static constexpr auto MIN_PATH_BUILD_INTERVAL = 500ms; static constexpr auto PATH_BUILD_RATE = 100ms; + /// limiter for path builds + /// prevents overload and such + class BuildLimiter + { + util::DecayingHashSet m_EdgeLimiter; + + public: + /// attempt a build + /// return true if we are allowed to continue + bool + Attempt(const RouterID& router); + + /// decay limit entries + void + Decay(llarp_time_t now); + + /// return true if this router is currently limited + bool + Limited(const RouterID& router) const; + }; + struct Builder : public PathSet { private: llarp_time_t m_LastWarn = 0s; - util::DecayingHashSet m_EdgeLimiter; protected: /// flag for PathSet::Stop() diff --git a/llarp/path/pathset.cpp b/llarp/path/pathset.cpp index 55c0b8b73..f5f788551 100644 --- a/llarp/path/pathset.cpp +++ b/llarp/path/pathset.cpp @@ -85,9 +85,9 @@ namespace llarp if (itr->second->Expired(now)) { PathID_t txid = itr->second->TXID(); - router->outboundMessageHandler().QueueRemoveEmptyPath(std::move(txid)); + router->outboundMessageHandler().RemovePath(std::move(txid)); PathID_t rxid = itr->second->RXID(); - router->outboundMessageHandler().QueueRemoveEmptyPath(std::move(rxid)); + router->outboundMessageHandler().RemovePath(std::move(rxid)); itr = m_Paths.erase(itr); } else @@ -156,7 +156,8 @@ namespace llarp { if (chosen == nullptr) chosen = itr->second; - else if (chosen->intro.latency > itr->second->intro.latency) + else if ( + chosen->intro.latency != 0s and chosen->intro.latency > itr->second->intro.latency) chosen = itr->second; } } @@ -429,7 +430,7 @@ namespace llarp llarp_time_t minLatency = 30s; for (const auto& path : established) { - if (path->intro.latency < minLatency) + if (path->intro.latency < minLatency and path->intro.latency != 0s) { minLatency = path->intro.latency; chosen = path; diff --git a/llarp/profiling.hpp b/llarp/profiling.hpp index 1af23e82f..03372d3fb 100644 --- a/llarp/profiling.hpp +++ b/llarp/profiling.hpp @@ -50,17 +50,20 @@ namespace llarp { Profiling(); + inline static const int profiling_chances = 4; + /// generic variant bool - IsBad(const RouterID& r, uint64_t chances = 8) EXCLUDES(m_ProfilesMutex); + IsBad(const RouterID& r, uint64_t chances = profiling_chances) EXCLUDES(m_ProfilesMutex); /// check if this router should have paths built over it bool - IsBadForPath(const RouterID& r, uint64_t chances = 8) EXCLUDES(m_ProfilesMutex); + IsBadForPath(const RouterID& r, uint64_t chances = profiling_chances) EXCLUDES(m_ProfilesMutex); /// check if this router should be connected directly to bool - IsBadForConnect(const RouterID& r, uint64_t chances = 8) EXCLUDES(m_ProfilesMutex); + IsBadForConnect(const RouterID& r, uint64_t chances = profiling_chances) + EXCLUDES(m_ProfilesMutex); void MarkConnectTimeout(const RouterID& r) EXCLUDES(m_ProfilesMutex); diff --git a/llarp/router/abstractrouter.hpp b/llarp/router/abstractrouter.hpp index dff705206..9e9043b87 100644 --- a/llarp/router/abstractrouter.hpp +++ b/llarp/router/abstractrouter.hpp @@ -292,6 +292,16 @@ namespace llarp virtual bool ConnectionToRouterAllowed(const RouterID& router) const = 0; + /// return true if we have an exit as a client + virtual bool + HasClientExit() const + { + return false; + }; + + virtual path::BuildLimiter& + pathBuildLimiter() = 0; + /// return true if we have at least 1 session to this router in either /// direction virtual bool diff --git a/llarp/router/i_outbound_message_handler.hpp b/llarp/router/i_outbound_message_handler.hpp index 83f02d25c..0ab96c434 100644 --- a/llarp/router/i_outbound_message_handler.hpp +++ b/llarp/router/i_outbound_message_handler.hpp @@ -38,7 +38,7 @@ namespace llarp Tick() = 0; virtual void - QueueRemoveEmptyPath(const PathID_t& pathid) = 0; + RemovePath(const PathID_t& pathid) = 0; virtual util::StatusObject ExtractStatus() const = 0; diff --git a/llarp/router/i_rc_lookup_handler.hpp b/llarp/router/i_rc_lookup_handler.hpp index 42ad1f0ef..7883dd3b4 100644 --- a/llarp/router/i_rc_lookup_handler.hpp +++ b/llarp/router/i_rc_lookup_handler.hpp @@ -58,6 +58,9 @@ namespace llarp virtual size_t NumberOfStrictConnectRouters() const = 0; + + virtual bool + HaveReceivedWhitelist() const = 0; }; } // namespace llarp diff --git a/llarp/router/outbound_message_handler.cpp b/llarp/router/outbound_message_handler.cpp index 1e3180723..b676383dd 100644 --- a/llarp/router/outbound_message_handler.cpp +++ b/llarp/router/outbound_message_handler.cpp @@ -15,14 +15,17 @@ namespace llarp { const PathID_t OutboundMessageHandler::zeroID; + using namespace std::chrono_literals; + OutboundMessageHandler::OutboundMessageHandler(size_t maxQueueSize) - : outboundQueue(maxQueueSize), removedPaths(20), removedSomePaths(false) + : outboundQueue(maxQueueSize), recentlyRemovedPaths(5s), removedSomePaths(false) {} bool OutboundMessageHandler::QueueMessage( const RouterID& remote, const ILinkMessage& msg, SendStatusHandler callback) { + // if the destination is invalid, callback with failure and return if (not _linkManager->SessionIsClient(remote) and not _lookupHandler->RemoteIsAllowed(remote)) { DoCallback(callback, SendStatus::InvalidRouter); @@ -44,26 +47,31 @@ namespace llarp std::copy_n(buf.base, buf.sz, message.first.data()); + // if we have a session to the destination, queue the message and return if (_linkManager->HasSessionTo(remote)) { QueueOutboundMessage(remote, std::move(message), msg.pathid, priority); return true; } + // if we don't have a session to the destination, queue the message onto + // a special pending session queue for that destination, and then create + // that pending session if there is not already a session establish attempt + // in progress. bool shouldCreateSession = false; { util::Lock l(_mutex); // create queue for if it doesn't exist, and get iterator - auto itr_pair = pendingSessionMessageQueues.emplace(remote, MessageQueue()); + auto [queue_itr, is_new] = pendingSessionMessageQueues.emplace(remote, MessageQueue()); MessageQueueEntry entry; entry.priority = priority; entry.message = message; entry.router = remote; - itr_pair.first->second.push(std::move(entry)); + queue_itr->second.push(std::move(entry)); - shouldCreateSession = itr_pair.second; + shouldCreateSession = is_new; } if (shouldCreateSession) @@ -77,26 +85,33 @@ namespace llarp void OutboundMessageHandler::Tick() { - m_Killer.TryAccess([self = this]() { - self->ProcessOutboundQueue(); - self->RemoveEmptyPathQueues(); - self->SendRoundRobin(); + m_Killer.TryAccess([this]() { + recentlyRemovedPaths.Decay(); + ProcessOutboundQueue(); + SendRoundRobin(); }); } void - OutboundMessageHandler::QueueRemoveEmptyPath(const PathID_t& pathid) + OutboundMessageHandler::RemovePath(const PathID_t& pathid) { - m_Killer.TryAccess([self = this, pathid]() { - if (self->removedPaths.full()) + m_Killer.TryAccess([this, pathid]() { + /* add the path id to a list of recently removed paths to act as a filter + * for messages that are queued but haven't been sorted into path queues yet. + * + * otherwise these messages would re-create the path queue we just removed, and + * those path queues would be leaked / never removed. + */ + recentlyRemovedPaths.Insert(pathid); + auto itr = outboundMessageQueues.find(pathid); + if (itr != outboundMessageQueues.end()) { - self->RemoveEmptyPathQueues(); + outboundMessageQueues.erase(itr); } - self->removedPaths.pushBack(pathid); + removedSomePaths = true; }); } - // TODO: this util::StatusObject OutboundMessageHandler::ExtractStatus() const { @@ -241,6 +256,8 @@ namespace llarp { MessageQueueEntry entry; entry.message = std::move(msg); + + // copy callback in case we need to call it, so we can std::move(entry) auto callback_copy = entry.message.second; entry.router = remote; entry.pathid = pathid; @@ -266,17 +283,23 @@ namespace llarp { while (not outboundQueue.empty()) { - // TODO: can we add util::thread::Queue::front() for move semantics here? MessageQueueEntry entry = outboundQueue.popFront(); - auto itr_pair = outboundMessageQueues.emplace(entry.pathid, MessageQueue()); + // messages may still be queued for processing when a pathid is removed, + // so check here if the pathid was recently removed. + if (recentlyRemovedPaths.Contains(entry.pathid)) + { + return; + } + + auto [queue_itr, is_new] = outboundMessageQueues.emplace(entry.pathid, MessageQueue()); - if (itr_pair.second && !entry.pathid.IsZero()) + if (is_new && !entry.pathid.IsZero()) { roundRobinOrder.push(entry.pathid); } - MessageQueue& path_queue = itr_pair.first->second; + MessageQueue& path_queue = queue_itr->second; if (path_queue.size() < MAX_PATH_QUEUE_SIZE || entry.pathid.IsZero()) { @@ -290,41 +313,25 @@ namespace llarp } } - void - OutboundMessageHandler::RemoveEmptyPathQueues() - { - removedSomePaths = false; - if (removedPaths.empty()) - return; - - while (not removedPaths.empty()) - { - auto itr = outboundMessageQueues.find(removedPaths.popFront()); - if (itr != outboundMessageQueues.end()) - { - outboundMessageQueues.erase(itr); - } - } - removedSomePaths = true; - } - void OutboundMessageHandler::SendRoundRobin() { m_queueStats.numTicks++; - // send non-routing messages first priority - auto& non_routing_mq = outboundMessageQueues[zeroID]; - while (not non_routing_mq.empty()) + // send routing messages first priority + auto& routing_mq = outboundMessageQueues[zeroID]; + while (not routing_mq.empty()) { - const MessageQueueEntry& entry = non_routing_mq.top(); + const MessageQueueEntry& entry = routing_mq.top(); Send(entry.router, entry.message); - non_routing_mq.pop(); + routing_mq.pop(); } size_t empty_count = 0; size_t num_queues = roundRobinOrder.size(); + // if any paths have been removed since last tick, remove any stale + // entries from the round-robin ordering if (removedSomePaths) { for (size_t i = 0; i < num_queues; i++) @@ -338,6 +345,7 @@ namespace llarp } } } + removedSomePaths = false; num_queues = roundRobinOrder.size(); size_t sent_count = 0; @@ -346,7 +354,10 @@ namespace llarp return; } - while (sent_count < MAX_OUTBOUND_MESSAGES_PER_TICK) // TODO: better stop condition + // send messages for each pathid in roundRobinOrder, stopping when + // either every path's queue is empty or a set maximum amount of + // messages have been sent. + while (sent_count < MAX_OUTBOUND_MESSAGES_PER_TICK) { PathID_t pathid = std::move(roundRobinOrder.front()); roundRobinOrder.pop(); diff --git a/llarp/router/outbound_message_handler.hpp b/llarp/router/outbound_message_handler.hpp index a3cfa4bad..51beac9cc 100644 --- a/llarp/router/outbound_message_handler.hpp +++ b/llarp/router/outbound_message_handler.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -27,15 +28,45 @@ namespace llarp OutboundMessageHandler(size_t maxQueueSize = MAX_OUTBOUND_QUEUE_SIZE); + /* Called to queue a message to be sent to a router. + * + * If there is no session with the destination router, the message is added to a + * pending session queue for that router. If there is no pending session to that + * router, one is created. + * + * If there is a session to the destination router, the message is placed on the shared + * outbound message queue to be processed on Tick(). + * + * When this class' Tick() is called, that queue is emptied and the messages there + * are placed in their paths' respective individual queues. + * + * Returns false if encoding the message into a buffer fails, true otherwise. + * A return value of true merely means we successfully processed the queue request, + * so for example an invalid destination still yields a true return. + */ bool QueueMessage(const RouterID& remote, const ILinkMessage& msg, SendStatusHandler callback) override EXCLUDES(_mutex); + /* Called once per event loop tick. + * + * Processes messages on the shared message queue into their paths' respective + * individual queues. + * + * Removes the individual queues for paths which have died / expired, as informed by + * QueueRemoveEmptyPath. + * + * Sends all routing messages that have been queued, indicated by pathid 0 when queued. + * Sends messages from path queues until all are empty or a set cap has been reached. + */ void Tick() override; + /* Called from outside this class to inform it that a path has died / expired + * and its queue should be discarded. + */ void - QueueRemoveEmptyPath(const PathID_t& pathid) override; + RemovePath(const PathID_t& pathid) override; util::StatusObject ExtractStatus() const override; @@ -46,6 +77,9 @@ namespace llarp private: using Message = std::pair, SendStatusHandler>; + /* A message that has been queued for sending, but not yet + * processed into an individual path's message queue. + */ struct MessageQueueEntry { uint16_t priority; @@ -73,6 +107,14 @@ namespace llarp using MessageQueue = std::priority_queue; + /* If a session is not yet created with the destination router for a message, + * a special queue is created for that router and an attempt is made to + * establish a session. When this establish attempt concludes, either + * the messages are then sent to that router immediately, on success, or + * the messages are dropped and their send status callbacks are invoked with + * the appropriate send status. + */ + void OnSessionEstablished(const RouterID& router); @@ -91,6 +133,7 @@ namespace llarp void OnSessionResult(const RouterID& router, const SessionResult result); + /* queues a message's send result callback onto the event loop */ void DoCallback(SendStatusHandler callback, SendStatus status); @@ -100,30 +143,62 @@ namespace llarp bool EncodeBuffer(const ILinkMessage& msg, llarp_buffer_t& buf); + /* sends the message along to the link layer, and hopefully out to the network + * + * returns the result of the call to LinkManager::SendTo() + */ bool Send(const RouterID& remote, const Message& msg); + /* Sends the message along to the link layer if we have a session to the remote + * + * returns the result of the Send() call, or false if no session. + */ bool SendIfSession(const RouterID& remote, const Message& msg); + /* queues a message to the shared outbound message queue. + * + * If the queue is full, the message is dropped and the message's status + * callback is invoked with a congestion status. + * + * When this class' Tick() is called, that queue is emptied and the messages there + * are placed in their paths' respective individual queues. + */ bool QueueOutboundMessage( const RouterID& remote, Message&& msg, const PathID_t& pathid, uint16_t priority = 0); + /* Processes messages on the shared message queue into their paths' respective + * individual queues. + */ void ProcessOutboundQueue(); - void - RemoveEmptyPathQueues(); - + /* + * Sends all routing messages that have been queued, indicated by pathid 0 when queued. + * + * Sends messages from path queues until all are empty or a set cap has been reached. + * This will send one message from each queue in a round-robin fashion such that they + * all have roughly equal access to bandwidth. A notion of priority may be introduced + * at a later time, but for now only routing messages get priority. + */ void SendRoundRobin(); + /* Invoked when an outbound session establish attempt has concluded. + * + * If the outbound session was successfully created, sends any messages queued + * for that destination along to it. + * + * If the session was unsuccessful, invokes the send status callbacks of those + * queued messages and drops them. + */ void FinalizeSessionRequest(const RouterID& router, SendStatus status) EXCLUDES(_mutex); llarp::thread::Queue outboundQueue; - llarp::thread::Queue removedPaths; + llarp::util::DecayingHashSet recentlyRemovedPaths; bool removedSomePaths; mutable util::Mutex _mutex; // protects pendingSessionMessageQueues diff --git a/llarp/router/outbound_session_maker.cpp b/llarp/router/outbound_session_maker.cpp index c980ed592..8846a38ec 100644 --- a/llarp/router/outbound_session_maker.cpp +++ b/llarp/router/outbound_session_maker.cpp @@ -232,7 +232,7 @@ namespace llarp bool OutboundSessionMaker::ShouldConnectTo(const RouterID& router) const { - if (router == us) + if (router == us or not _rcLookup->RemoteIsAllowed(router)) return false; size_t numPending = 0; { diff --git a/llarp/router/rc_lookup_handler.cpp b/llarp/router/rc_lookup_handler.cpp index 12b8b1beb..76ae45053 100644 --- a/llarp/router/rc_lookup_handler.cpp +++ b/llarp/router/rc_lookup_handler.cpp @@ -49,7 +49,7 @@ namespace llarp } bool - RCLookupHandler::HaveReceivedWhitelist() + RCLookupHandler::HaveReceivedWhitelist() const { util::Lock l(_mutex); return not whitelistRouters.empty(); @@ -127,14 +127,12 @@ namespace llarp return false; } - util::Lock l(_mutex); + if (not useWhitelist) + return true; - if (useWhitelist && whitelistRouters.find(remote) == whitelistRouters.end()) - { - return false; - } + util::Lock lock{_mutex}; - return true; + return whitelistRouters.count(remote); } bool diff --git a/llarp/router/rc_lookup_handler.hpp b/llarp/router/rc_lookup_handler.hpp index a3b88e924..cc0bed223 100644 --- a/llarp/router/rc_lookup_handler.hpp +++ b/llarp/router/rc_lookup_handler.hpp @@ -44,7 +44,7 @@ namespace llarp SetRouterWhitelist(const std::vector& routers) override EXCLUDES(_mutex); bool - HaveReceivedWhitelist(); + HaveReceivedWhitelist() const override; void GetRC(const RouterID& router, RCRequestCallback callback, bool forceLookup = false) override diff --git a/llarp/router/route_poker.cpp b/llarp/router/route_poker.cpp index 6ad13d147..a49eec7f7 100644 --- a/llarp/router/route_poker.cpp +++ b/llarp/router/route_poker.cpp @@ -1,5 +1,6 @@ #include "route_poker.hpp" #include "abstractrouter.hpp" +#include "net/sock_addr.hpp" #include #include #include @@ -119,7 +120,9 @@ namespace llarp const auto maybe = GetDefaultGateway(); if (not maybe.has_value()) { +#ifndef ANDROID LogError("Network is down"); +#endif // mark network lost m_HasNetwork = false; return; @@ -157,6 +160,11 @@ namespace llarp Update(); m_Enabling = false; m_Enabled = true; + + systemd_resolved_set_dns( + m_Router->hiddenServiceContext().GetDefault()->GetIfName(), + m_Router->GetConfig()->dns.m_bind.createSockAddr(), + true /* route all DNS */); } void @@ -167,6 +175,11 @@ namespace llarp DisableAllRoutes(); m_Enabled = false; + + systemd_resolved_set_dns( + m_Router->hiddenServiceContext().GetDefault()->GetIfName(), + m_Router->GetConfig()->dns.m_bind.createSockAddr(), + false /* route DNS only for .loki/.snode */); } void diff --git a/llarp/router/route_poker.hpp b/llarp/router/route_poker.hpp index 979d59d65..f494fada8 100644 --- a/llarp/router/route_poker.hpp +++ b/llarp/router/route_poker.hpp @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include "systemd_resolved.hpp" namespace llarp { diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 58a823a8b..bff2aa155 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -374,9 +374,21 @@ namespace llarp return inbound_routing_msg_parser.ParseMessageBuffer(buf, h, rxid, this); } + bool + Router::LooksDeregistered() const + { + return IsServiceNode() and whitelistRouters and _rcLookupHandler.HaveReceivedWhitelist() + and not _rcLookupHandler.RemoteIsAllowed(pubkey()); + } + bool Router::ConnectionToRouterAllowed(const RouterID& router) const { + if (LooksDeregistered()) + { + // we are deregistered don't allow any connections outbound at all + return false; + } return _rcLookupHandler.RemoteIsAllowed(router); } @@ -735,7 +747,8 @@ namespace llarp { ss << " snode | known/svc/clients: " << nodedb()->NumLoaded() << "/" << NumberOfConnectedRouters() << "/" << NumberOfConnectedClients() << " | " - << pathContext().CurrentTransitPaths() << " active paths"; + << pathContext().CurrentTransitPaths() << " active paths | " + << "block " << m_lokidRpcClient->BlockHeight(); } else { @@ -752,6 +765,8 @@ namespace llarp } #endif + m_PathBuildLimiter.Decay(now); + routerProfiling().Tick(); if (ShouldReportStats(now)) @@ -763,7 +778,9 @@ namespace llarp _rcLookupHandler.PeriodicUpdate(now); + const bool gotWhitelist = _rcLookupHandler.HaveReceivedWhitelist(); const bool isSvcNode = IsServiceNode(); + const bool looksDeregistered = LooksDeregistered(); if (_rc.ExpiresSoon(now, std::chrono::milliseconds(randint() % 10000)) || (now - _rc.last_updated) > rcRegenInterval) @@ -772,11 +789,10 @@ namespace llarp if (!UpdateOurRC(false)) LogError("Failed to update our RC"); } - else + else if (not looksDeregistered) { GossipRCIfNeeded(_rc); } - const bool gotWhitelist = _rcLookupHandler.HaveReceivedWhitelist(); // remove RCs for nodes that are no longer allowed by network policy nodedb()->RemoveIf([&](const RouterContact& rc) -> bool { // don't purge bootstrap nodes from nodedb @@ -844,7 +860,7 @@ namespace llarp const int interval = isSvcNode ? 5 : 2; const auto timepoint_now = Clock_t::now(); - if (timepoint_now >= m_NextExploreAt) + if (timepoint_now >= m_NextExploreAt and not looksDeregistered) { _rcLookupHandler.ExploreNetwork(); m_NextExploreAt = timepoint_now + std::chrono::seconds(interval); @@ -856,7 +872,17 @@ namespace llarp connectToNum = strictConnect; } - if (connected < connectToNum) + if (looksDeregistered) + { + // kill all sessions that are open because we think we are deregistered + _linkManager.ForEachPeer([](auto* peer) { + if (peer) + peer->Close(); + }); + // complain about being deregistered + LogError("We are running as a service node but we seem to be decommissioned"); + } + else if (connected < connectToNum) { size_t dlt = connectToNum - connected; LogDebug("connecting to ", dlt, " random routers to keep alive"); @@ -883,7 +909,16 @@ namespace llarp if (m_peerDb->shouldFlush(now)) { LogDebug("Queing database flush..."); - QueueDiskIO([this]() { m_peerDb->flushDatabase(); }); + QueueDiskIO([this]() { + try + { + m_peerDb->flushDatabase(); + } + catch (std::exception& ex) + { + LogError("Could not flush peer stats database: ", ex.what()); + } + }); } } diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index b0dd03575..b1277ed69 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -74,6 +74,14 @@ namespace llarp LMQ_ptr m_lmq; + path::BuildLimiter m_PathBuildLimiter; + + path::BuildLimiter& + pathBuildLimiter() override + { + return m_PathBuildLimiter; + } + const LMQ_ptr& lmq() const override { @@ -173,6 +181,10 @@ namespace llarp void QueueDiskIO(std::function func) override; + /// return true if we look like we are a deregistered service node + bool + LooksDeregistered() const; + std::optional _ourAddress; EventLoop_ptr _loop; @@ -390,7 +402,7 @@ namespace llarp /// return true if we are a client with an exit configured bool - HasClientExit() const; + HasClientExit() const override; const byte_t* pubkey() const override diff --git a/llarp/router/systemd_resolved.cpp b/llarp/router/systemd_resolved.cpp new file mode 100644 index 000000000..d0ee535c2 --- /dev/null +++ b/llarp/router/systemd_resolved.cpp @@ -0,0 +1,171 @@ +#include "systemd_resolved.hpp" +#include + +#ifndef WITH_SYSTEMD + +namespace llarp +{ + bool + systemd_resolved_set_dns(std::string, llarp::SockAddr, bool) + { + LogDebug("lokinet is not built with systemd support, cannot set systemd resolved DNS"); + return false; + } +} // namespace llarp + +#else + +#include + +extern "C" +{ +#include +#include +} + +using namespace std::literals; + +namespace llarp +{ + namespace + { + template + void + resolved_call(sd_bus* bus, const char* method, const char* arg_format, T... args) + { + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message* msg = nullptr; + int r = sd_bus_call_method( + bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + method, + &error, + &msg, + arg_format, + args...); + + if (r < 0) + throw std::runtime_error{"sdbus resolved "s + method + " failed: " + strerror(-r)}; + + sd_bus_message_unref(msg); + sd_bus_error_free(&error); + } + + struct sd_bus_deleter + { + void + operator()(sd_bus* ptr) const + { + sd_bus_unref(ptr); + } + }; + } // namespace + + bool + systemd_resolved_set_dns(std::string ifname, llarp::SockAddr dns, bool global) + { + unsigned int if_ndx = if_nametoindex(ifname.c_str()); + if (if_ndx == 0) + { + LogWarn("No such interface '", ifname, "'"); + return false; + } + + // Connect to the system bus + sd_bus* bus = nullptr; + int r = sd_bus_open_system(&bus); + if (r < 0) + { + LogWarn("Failed to connect to system bus to set DNS: ", strerror(-r)); + return false; + } + std::unique_ptr bus_ptr{bus}; + + try + { + // This passing address by bytes and using two separate calls for ipv4/ipv6 is gross, but the + // alternative is to build up a bunch of crap with va_args, which is slightly more gross. + if (dns.isIPv6()) + { + auto ipv6 = dns.getIPv6(); + static_assert(sizeof(ipv6) == 16); + auto* a = reinterpret_cast(&ipv6); + resolved_call( + bus, + "SetLinkDNSEx", + "ia(iayqs)", + (int32_t)if_ndx, + (int)1, // number of "iayqs"s we are passing + (int32_t)AF_INET6, // network address type + (int)16, // network addr byte size + // clang-format off + a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], + a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], // yuck + // clang-format on + (uint16_t)dns.getPort(), + nullptr // dns server name (for TLS SNI which we don't care about) + ); + } + else + { + auto ipv4 = dns.getIPv4(); + static_assert(sizeof(ipv4) == 4); + auto* a = reinterpret_cast(&ipv4); + resolved_call( + bus, + "SetLinkDNSEx", + "ia(iayqs)", + (int32_t)if_ndx, + (int)1, // number of "iayqs"s we are passing + (int32_t)AF_INET, // network address type + (int)4, // network addr byte size + // clang-format off + a[0], a[1], a[2], a[3], // yuck + // clang-format on + (uint16_t)dns.getPort(), + nullptr // dns server name (for TLS SNI which we don't care about) + ); + } + + if (global) + // Setting "." as a routing domain gives this DNS server higher priority in resolution + // compared to dns servers that are set without a domain (e.g. the default for a + // DHCP-configured DNS server) + resolved_call( + bus, + "SetLinkDomains", + "ia(sb)", + (int32_t)if_ndx, + (int)1, // array size + "." // global DNS root + ); + else + // Only resolve .loki and .snode through lokinet (so you keep using your local DNS server + // for everything else, which is nicer than forcing everything though lokinet's upstream + // DNS). + resolved_call( + bus, + "SetLinkDomains", + "ia(sb)", + (int32_t)if_ndx, + (int)2, // array size + "loki", // domain + (int)1, // routing domain = true + "snode", // domain + (int)1 // routing domain = true + ); + + return true; + } + catch (const std::exception& e) + { + LogWarn("Failed to set DNS via systemd-resolved: ", e.what()); + } + return false; + } + +} // namespace llarp + +#endif // WITH_SYSTEMD diff --git a/llarp/router/systemd_resolved.hpp b/llarp/router/systemd_resolved.hpp new file mode 100644 index 000000000..8fc8c20af --- /dev/null +++ b/llarp/router/systemd_resolved.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace llarp +{ + /// Attempts to set lokinet as the DNS server for systemd-resolved. Returns true if successful, + /// false if unsupported or fails. (When compiled without systemd support this always returns + /// false without doing anything). + /// + /// \param if_name -- the interface name to which we add the DNS servers, e.g. lokitun0. + /// Typically tun_endpoint.GetIfName(). + /// \param dns -- the listening address of the lokinet DNS server + /// \param global -- whether to set up lokinet for all DNS queries (true) or just .loki & .snode + /// addresses (false). + bool + systemd_resolved_set_dns(std::string if_name, llarp::SockAddr dns, bool global); +} // namespace llarp diff --git a/llarp/rpc/lokid_rpc_client.cpp b/llarp/rpc/lokid_rpc_client.cpp index 691115f81..5ee2676da 100644 --- a/llarp/rpc/lokid_rpc_client.cpp +++ b/llarp/rpc/lokid_rpc_client.cpp @@ -45,6 +45,7 @@ namespace llarp auto lokidCategory = m_lokiMQ->add_category("lokid", oxenmq::Access{oxenmq::AuthLevel::none}); lokidCategory.add_request_command( "get_peer_stats", [this](oxenmq::Message& m) { HandleGetPeerStats(m); }); + m_UpdatingList = false; } void @@ -82,8 +83,20 @@ namespace llarp " parts instead of 2 parts so we will not update the list of service nodes"); return; // bail } - LogDebug("new block at hieght ", msg.data[0]); - UpdateServiceNodeList(std::string{msg.data[1]}); + try + { + m_BlockHeight = std::stoll(std::string{msg.data[0]}); + } + catch (std::exception& ex) + { + LogError("bad block hieght: ", ex.what()); + return; // bail + } + + LogDebug("new block at hieght ", m_BlockHeight); + // don't upadate on block notification if an update is pending + if (not m_UpdatingList) + UpdateServiceNodeList(std::string{msg.data[1]}); } void @@ -95,9 +108,11 @@ namespace llarp request["active_only"] = true; if (not topblock.empty()) request["poll_block_hash"] = topblock; + m_UpdatingList = true; Request( "rpc.get_service_nodes", [self = shared_from_this()](bool success, std::vector data) { + self->m_UpdatingList = false; if (not success) { LogWarn("failed to update service node list"); diff --git a/llarp/rpc/lokid_rpc_client.hpp b/llarp/rpc/lokid_rpc_client.hpp index 08c84e9f1..b05755dd7 100644 --- a/llarp/rpc/lokid_rpc_client.hpp +++ b/llarp/rpc/lokid_rpc_client.hpp @@ -30,6 +30,13 @@ namespace llarp SecretKey ObtainIdentityKey(); + /// get what the current block height is according to oxend + uint64_t + BlockHeight() const + { + return m_BlockHeight; + } + void LookupLNSNameHash( dht::Key_t namehash, @@ -76,6 +83,9 @@ namespace llarp LMQ_ptr m_lokiMQ; AbstractRouter* const m_Router; + std::atomic m_UpdatingList; + + uint64_t m_BlockHeight; }; } // namespace rpc diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 9cc244fb2..ef5977dbc 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -402,7 +402,7 @@ namespace llarp::rpc { service::Address addr; const auto exit_str = exit_itr->get(); - if (service::NameIsValid(exit_str)) + if (service::NameIsValid(exit_str) or exit_str == "null") { lnsExit = exit_str; } @@ -456,39 +456,54 @@ namespace llarp::rpc { auto mapExit = [=](service::Address addr) mutable { ep->MapExitRange(range, addr); + r->routePoker().Enable(); + r->routePoker().Up(); bool shouldSendAuth = false; if (token.has_value()) { shouldSendAuth = true; ep->SetAuthInfoForEndpoint(*exit, service::AuthInfo{*token}); } + auto onGoodResult = [r, reply](std::string reason) { + if (r->HasClientExit()) + reply(CreateJSONResponse(reason)); + else + reply(CreateJSONError("we dont have an exit?")); + }; + auto onBadResult = [r, reply, ep, range](std::string reason) { + r->routePoker().Down(); + ep->UnmapExitRange(range); + reply(CreateJSONError(reason)); + }; + if (addr.IsZero()) + { + onGoodResult("added null exit"); + return; + } ep->EnsurePathToService( addr, - [reply, r, shouldSendAuth](auto, service::OutboundContext* ctx) { + [onBadResult, onGoodResult, shouldSendAuth, addrStr = addr.ToString()]( + auto, service::OutboundContext* ctx) { if (ctx == nullptr) { - reply(CreateJSONError("could not find exit")); + onBadResult("could not find exit"); return; } - auto onGoodResult = [r, reply](std::string reason) { - r->routePoker().Enable(); - r->routePoker().Up(); - reply(CreateJSONResponse(reason)); - }; if (not shouldSendAuth) { - onGoodResult("OK"); + onGoodResult("OK: connected to " + addrStr); return; } - ctx->AsyncSendAuth([onGoodResult, reply](service::AuthResult result) { - // TODO: refactor this code. We are 5 lambdas deep here! - if (result.code != service::AuthResultCode::eAuthAccepted) - { - reply(CreateJSONError(result.reason)); - return; - } - onGoodResult(result.reason); - }); + ctx->AsyncSendAuth( + [onGoodResult, onBadResult](service::AuthResult result) { + // TODO: refactor this code. We are 5 lambdas deep here! + if (result.code != service::AuthResultCode::eAuthAccepted) + { + onBadResult(result.reason); + return; + } + onGoodResult(result.reason); + }); }, 5s); }; @@ -521,7 +536,6 @@ namespace llarp::rpc else { reply(CreateJSONError("lns name resolved to a snode")); - return; } }); } diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 32e9bfd76..29b24262c 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -46,11 +46,12 @@ namespace llarp namespace service { Endpoint::Endpoint(AbstractRouter* r, Context* parent) - : path::Builder(r, 3, path::default_len) - , context(parent) - , m_InboundTrafficQueue(512) - , m_SendQueue(512) - , m_RecvQueue(512) + : path::Builder{r, 3, path::default_len} + , context{parent} + , m_InboundTrafficQueue{512} + , m_SendQueue{512} + , m_RecvQueue{512} + , m_IntrosetLookupFilter{5s} { m_state = std::make_unique(); m_state->m_Router = r; @@ -283,7 +284,8 @@ namespace llarp { RegenAndPublishIntroSet(); } - + // decay introset lookup filter + m_IntrosetLookupFilter.Decay(now); // expire name cache m_state->nameCache.Decay(now); // expire snode sessions @@ -518,13 +520,15 @@ namespace llarp void Endpoint::ConvoTagTX(const ConvoTag& tag) { - Sessions()[tag].TX(); + if (Sessions().count(tag)) + Sessions()[tag].TX(); } void Endpoint::ConvoTagRX(const ConvoTag& tag) { - Sessions()[tag].RX(); + if (Sessions().count(tag)) + Sessions()[tag].RX(); } bool @@ -620,8 +624,12 @@ namespace llarp Endpoint* m_Endpoint; uint64_t m_relayOrder; PublishIntroSetJob( - Endpoint* parent, uint64_t id, EncryptedIntroSet introset, uint64_t relayOrder) - : IServiceLookup(parent, id, "PublishIntroSet") + Endpoint* parent, + uint64_t id, + EncryptedIntroSet introset, + uint64_t relayOrder, + llarp_time_t timeout) + : IServiceLookup(parent, id, "PublishIntroSet", timeout) , m_IntroSet(std::move(introset)) , m_Endpoint(parent) , m_relayOrder(relayOrder) @@ -663,6 +671,8 @@ namespace llarp } } + constexpr auto PublishIntrosetTimeout = 20s; + bool Endpoint::PublishIntroSetVia( const EncryptedIntroSet& introset, @@ -670,7 +680,8 @@ namespace llarp path::Path_ptr path, uint64_t relayOrder) { - auto job = new PublishIntroSetJob(this, GenTXID(), introset, relayOrder); + auto job = + new PublishIntroSetJob(this, GenTXID(), introset, relayOrder, PublishIntrosetTimeout); if (job->SendRequestViaPath(path, r)) { m_state->m_LastPublishAttempt = Now(); @@ -747,6 +758,8 @@ namespace llarp path::Builder::PathBuildStarted(path); } + constexpr auto MaxOutboundContextPerRemote = 4; + void Endpoint::PutNewOutboundContext(const service::IntroSet& introset, llarp_time_t left) { @@ -755,7 +768,7 @@ namespace llarp auto& remoteSessions = m_state->m_RemoteSessions; auto& serviceLookups = m_state->m_PendingServiceLookups; - if (remoteSessions.count(addr) >= MAX_OUTBOUND_CONTEXT_COUNT) + if (remoteSessions.count(addr) >= MaxOutboundContextPerRemote) { auto itr = remoteSessions.find(addr); @@ -930,9 +943,10 @@ namespace llarp if (result) { var::visit( - [&](auto&& value) { + [&result, &cache, name](auto&& value) { if (value.IsZero()) { + cache.Remove(name); result = std::nullopt; } }, @@ -942,10 +956,6 @@ namespace llarp { cache.Put(name, *result); } - else - { - cache.Remove(name); - } handler(result); }; @@ -955,7 +965,7 @@ namespace llarp for (const auto& path : paths) { LogInfo(Name(), " lookup ", name, " from ", path->Endpoint()); - auto job = new LookupNameJob(this, GenTXID(), name, resultHandler); + auto job = new LookupNameJob{this, GenTXID(), name, resultHandler}; job->SendRequestViaPath(path, m_router); } } @@ -1003,7 +1013,7 @@ namespace llarp msg.S = path->NextSeqNo(); if (path && path->SendRoutingMessage(msg, Router())) { - RouterLookupJob job(this, handler); + RouterLookupJob job{this, handler}; assert(msg.M.size() == 1); auto dhtMsg = dynamic_cast(msg.M[0].get()); @@ -1011,7 +1021,7 @@ namespace llarp m_router->NotifyRouterEvent(m_router->pubkey(), *dhtMsg); - routers.emplace(router, RouterLookupJob(this, handler)); + routers.emplace(router, std::move(job)); return true; } } @@ -1164,6 +1174,9 @@ namespace llarp Endpoint::SendAuthResult( path::Path_ptr path, PathID_t replyPath, ConvoTag tag, AuthResult result) { + // not applicable because we are not an exit or don't have an endpoint auth policy + if ((not m_state->m_ExitEnabled) or m_AuthPolicy == nullptr) + return; ProtocolFrame f; f.R = AuthResultCodeAsInt(result.code); f.T = tag; @@ -1263,6 +1276,7 @@ namespace llarp void Endpoint::HandlePathDied(path::Path_ptr p) { + m_router->routerProfiling().MarkPathTimeout(p.get()); ManualRebuild(1); RegenAndPublishIntroSet(); path::Builder::HandlePathDied(p); @@ -1346,13 +1360,8 @@ namespace llarp // add response hook to list for address. m_state->m_PendingServiceLookups.emplace(remote, hook); - auto& lookupTimes = m_state->m_LastServiceLookupTimes; - const auto now = Now(); - - // if most recent lookup was within last INTROSET_LOOKUP_RETRY_COOLDOWN - // just add callback to the list and return - if (lookupTimes.find(remote) != lookupTimes.end() - && now < (lookupTimes[remote] + INTROSET_LOOKUP_RETRY_COOLDOWN)) + /// check replay filter + if (not m_IntrosetLookupFilter.Insert(remote)) return true; const auto paths = GetManyPathsWithUniqueEndpoints(this, NumParallelLookups); @@ -1376,6 +1385,7 @@ namespace llarp }, location, PubKey{remote.as_array()}, + path->Endpoint(), order, GenTXID(), timeout); @@ -1391,12 +1401,7 @@ namespace llarp order++; if (job->SendRequestViaPath(path, Router())) { - if (not hookAdded) - { - // if any of the lookups is successful, set last lookup time - lookupTimes[remote] = now; - hookAdded = true; - } + hookAdded = true; } else LogError(Name(), " send via path failed for lookup"); @@ -1608,8 +1613,8 @@ namespace llarp } if (session.inbound) { - auto path = GetPathByRouter(session.intro.router); - if (path) + auto path = GetPathByRouter(session.replyIntro.router); + if (path and path->IsReady()) { const auto rttEstimate = (session.replyIntro.latency + path->intro.latency) * 2; if (rttEstimate < rtt) @@ -1618,10 +1623,6 @@ namespace llarp rtt = rttEstimate; } } - else - { - LogWarn("no path for inbound session T=", tag); - } } else { @@ -1849,7 +1850,7 @@ namespace llarp } self->m_state->m_PendingTraffic.erase(addr); }, - 1500ms); + PathAlignmentTimeout()); return true; } LogDebug("SendOrQueue failed: no inbound/outbound sessions"); diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 6f17bec07..9de2e05a8 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -61,8 +61,6 @@ namespace llarp public IDataHandler, public EndpointBase { - static const size_t MAX_OUTBOUND_CONTEXT_COUNT = 1; - Endpoint(AbstractRouter* r, Context* parent); ~Endpoint() override; @@ -308,6 +306,13 @@ namespace llarp bool ShouldBuildMore(llarp_time_t now) const override; + virtual llarp_time_t + PathAlignmentTimeout() const + { + constexpr auto DefaultPathAlignmentTimeout = 30s; + return DefaultPathAlignmentTimeout; + } + bool EnsurePathTo( std::variant addr, @@ -525,6 +530,9 @@ namespace llarp ConvoMap& Sessions(); // clang-format on thread::Queue m_RecvQueue; + + /// for rate limiting introset lookups + util::DecayingHashSet
m_IntrosetLookupFilter; }; using Endpoint_ptr = std::shared_ptr; diff --git a/llarp/service/endpoint_util.hpp b/llarp/service/endpoint_util.hpp index 7f01a65a6..2aa66bfd4 100644 --- a/llarp/service/endpoint_util.hpp +++ b/llarp/service/endpoint_util.hpp @@ -50,7 +50,7 @@ namespace llarp { --tries; const auto path = ep->PickRandomEstablishedPath(); - if (path) + if (path and path->IsReady()) paths.emplace(path); } while (tries > 0 and paths.size() < N); return paths; diff --git a/llarp/service/hidden_service_address_lookup.cpp b/llarp/service/hidden_service_address_lookup.cpp index 2983bc1de..d1a294797 100644 --- a/llarp/service/hidden_service_address_lookup.cpp +++ b/llarp/service/hidden_service_address_lookup.cpp @@ -13,6 +13,7 @@ namespace llarp HandlerFunc h, const dht::Key_t& l, const PubKey& k, + const RouterID& ep, uint64_t order, uint64_t tx, llarp_time_t timeout) @@ -21,13 +22,15 @@ namespace llarp , relayOrder(order) , location(l) , handle(std::move(h)) - {} + { + endpoint = ep; + } bool HiddenServiceAddressLookup::HandleIntrosetResponse(const std::set& results) { std::optional found; - const Address remote(rootkey); + const Address remote{rootkey}; if (results.size() > 0) { EncryptedIntroSet selected; diff --git a/llarp/service/hidden_service_address_lookup.hpp b/llarp/service/hidden_service_address_lookup.hpp index 5512c5657..fcc331f6d 100644 --- a/llarp/service/hidden_service_address_lookup.hpp +++ b/llarp/service/hidden_service_address_lookup.hpp @@ -23,6 +23,7 @@ namespace llarp HandlerFunc h, const dht::Key_t& location, const PubKey& rootkey, + const RouterID& routerAsked, uint64_t relayOrder, uint64_t tx, llarp_time_t timeout); diff --git a/llarp/service/outbound_context.cpp b/llarp/service/outbound_context.cpp index b35be22bf..15b16fdbe 100644 --- a/llarp/service/outbound_context.cpp +++ b/llarp/service/outbound_context.cpp @@ -45,20 +45,19 @@ namespace llarp if (dst == remoteIntro.pathID && remoteIntro.router == p->Endpoint()) { LogWarn(Name(), " message ", seq, " dropped by endpoint ", p->Endpoint(), " via ", dst); - if (MarkCurrentIntroBad(Now())) - { - SwapIntros(); - } - UpdateIntroSet(); + MarkCurrentIntroBad(Now()); + ShiftIntroduction(false); } return true; } + constexpr auto OutboundContextNumPaths = 2; + OutboundContext::OutboundContext(const IntroSet& introset, Endpoint* parent) - : path::Builder(parent->Router(), 4, parent->numHops) - , SendContext(introset.addressKeys, {}, this, parent) - , location(introset.addressKeys.Addr().ToKey()) - , currentIntroSet(introset) + : path::Builder{parent->Router(), OutboundContextNumPaths, parent->numHops} + , SendContext{introset.addressKeys, {}, this, parent} + , location{introset.addressKeys.Addr().ToKey()} + , currentIntroSet{introset} { updatingIntroSet = false; @@ -243,8 +242,12 @@ namespace llarp void OutboundContext::UpdateIntroSet() { - if (updatingIntroSet || markedBad) + constexpr auto IntrosetUpdateInterval = 10s; + const auto now = Now(); + if (updatingIntroSet or markedBad or now < m_LastIntrosetUpdateAt + IntrosetUpdateInterval) return; + LogInfo(Name(), " updating introset"); + m_LastIntrosetUpdateAt = now; const auto addr = currentIntroSet.addressKeys.Addr(); // we want to use the parent endpoint's paths because outbound context // does not implement path::PathSet::HandleGotIntroMessage @@ -257,6 +260,7 @@ namespace llarp util::memFn(&OutboundContext::OnIntroSetUpdate, shared_from_this()), location, PubKey{addr.as_array()}, + path->Endpoint(), relayOrder, m_Endpoint->GenTXID(), 5s); @@ -410,40 +414,17 @@ namespace llarp return t >= now + path::default_lifetime / 4; } - bool + void OutboundContext::MarkCurrentIntroBad(llarp_time_t now) { - return MarkIntroBad(remoteIntro, now); + MarkIntroBad(remoteIntro, now); } - bool + void OutboundContext::MarkIntroBad(const Introduction& intro, llarp_time_t now) { // insert bad intro m_BadIntros[intro] = now; - // try shifting intro without rebuild - if (ShiftIntroduction(false)) - { - // we shifted - // check if we have a path to the next intro router - if (GetNewestPathByRouter(m_NextIntro.router)) - return true; - // we don't have a path build one if we aren't building too fast - if (!BuildCooldownHit(now)) - BuildOneAlignedTo(m_NextIntro.router); - return true; - } - - // we didn't shift check if we should update introset - if (now - lastShift >= MIN_SHIFT_INTERVAL || currentIntroSet.HasExpiredIntros(now) - || currentIntroSet.IsExpired(now)) - { - // update introset - LogInfo(Name(), " updating introset"); - UpdateIntroSet(); - return true; - } - return false; } bool @@ -587,7 +568,7 @@ namespace llarp // verify source if (!frame.Verify(si)) { - LogWarn("signature failed"); + LogWarn("signature verification failed, T=", frame.T); return false; } // remove convotag it doesn't exist diff --git a/llarp/service/outbound_context.hpp b/llarp/service/outbound_context.hpp index cf374b60c..011aa74b8 100644 --- a/llarp/service/outbound_context.hpp +++ b/llarp/service/outbound_context.hpp @@ -60,10 +60,10 @@ namespace llarp ShiftIntroRouter(const RouterID remote); /// mark the current remote intro as bad - bool + void MarkCurrentIntroBad(llarp_time_t now) override; - bool + void MarkIntroBad(const Introduction& marked, llarp_time_t now); /// return true if we are ready to send @@ -153,6 +153,7 @@ namespace llarp bool m_GotInboundTraffic = false; bool sentIntro = false; std::function m_ReadyHook; + llarp_time_t m_LastIntrosetUpdateAt = 0s; }; } // namespace service diff --git a/llarp/service/sendcontext.hpp b/llarp/service/sendcontext.hpp index c946efd86..517bea988 100644 --- a/llarp/service/sendcontext.hpp +++ b/llarp/service/sendcontext.hpp @@ -65,7 +65,7 @@ namespace llarp virtual void UpdateIntroSet() = 0; - virtual bool + virtual void MarkCurrentIntroBad(llarp_time_t now) = 0; void diff --git a/llarp/service/session.cpp b/llarp/service/session.cpp index bf710dcdd..6238a2166 100644 --- a/llarp/service/session.cpp +++ b/llarp/service/session.cpp @@ -27,7 +27,7 @@ namespace llarp const auto lastUsed = std::max(lastSend, lastRecv); if (lastUsed == 0s) return intro.IsExpired(now); - return now > lastUsed && (now - lastUsed > lifetime || intro.IsExpired(now)); + return now >= lastUsed && (now - lastUsed > lifetime); } void diff --git a/llarp/util/decaying_hashset.hpp b/llarp/util/decaying_hashset.hpp index 8d5d0a5b1..e9561f4f5 100644 --- a/llarp/util/decaying_hashset.hpp +++ b/llarp/util/decaying_hashset.hpp @@ -45,7 +45,6 @@ namespace llarp if (now == 0s) now = llarp::time_now_ms(); EraseIf([&](const auto& item) { return (m_CacheInterval + item.second) <= now; }); - m_Values.rehash(0); } Time_t diff --git a/llarp/util/logging/logger.hpp b/llarp/util/logging/logger.hpp index 99c18b424..599242147 100644 --- a/llarp/util/logging/logger.hpp +++ b/llarp/util/logging/logger.hpp @@ -24,7 +24,7 @@ namespace llarp LogContext(); LogLevel curLevel = eLogInfo; LogLevel startupLevel = eLogInfo; - LogLevel runtimeLevel = eLogInfo; + LogLevel runtimeLevel = eLogWarn; ILogStream_ptr logStream; std::string nodeName = "lokinet"; diff --git a/test/net/test_llarp_net.cpp b/test/net/test_llarp_net.cpp index aea061bd4..4e5b92902 100644 --- a/test/net/test_llarp_net.cpp +++ b/test/net/test_llarp_net.cpp @@ -80,11 +80,6 @@ TEST_CASE("Bogon") REQUIRE(llarp::IsIPv4Bogon(llarp::ipaddr_ipv4_bits(192, 168, 1, 111))); } - SECTION("Bogon_DoD_8") - { - REQUIRE(llarp::IsIPv4Bogon(llarp::ipaddr_ipv4_bits(21, 3, 37, 70))); - } - SECTION("Bogon_127_8") { REQUIRE(llarp::IsIPv4Bogon(llarp::ipaddr_ipv4_bits(127, 0, 0, 1))); diff --git a/test/regress/2020-06-08-key-backup-bug.cpp b/test/regress/2020-06-08-key-backup-bug.cpp index 0de1fc85b..e4ebfaa97 100644 --- a/test/regress/2020-06-08-key-backup-bug.cpp +++ b/test/regress/2020-06-08-key-backup-bug.cpp @@ -6,7 +6,7 @@ llarp::RuntimeOptions opts = {false, false, false}; -/// make a llarp_main* with 1 endpoint that specifies a keyfile +/// make a context with 1 endpoint that specifies a keyfile static std::shared_ptr make_context(std::optional keyfile) {