Repository: oxen-io/loki-network Branch: dev Commit: 8b592d5c06d2 Files: 503 Total size: 2.0 MB Directory structure: gitextract_c7sekzv8/ ├── .clang-format ├── .clang-tidy ├── .dir-locals.el ├── .dockerignore ├── .drone.jsonnet ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ └── clean_issues.yml ├── .gitignore ├── .gitmodules ├── .swift-version ├── CMakeLists.txt ├── CONTRIBUTING.md ├── CONTRIBUTING_es.md ├── LICENSE ├── cmake/ │ ├── CMakeGraphVizOptions.cmake │ ├── DownloadLibSodium.cmake │ ├── FindJemalloc.cmake │ ├── GenVersion.cmake │ ├── MacroEnsureOutOfSourceBuild.cmake │ ├── StaticBuild.cmake │ ├── TargetArch.cmake │ ├── Version.cmake │ ├── add_import_library.cmake │ ├── cmake_uninstall.cmake.in │ ├── coverage.cmake │ ├── cross_compile.cmake │ ├── enable_lto.cmake │ ├── gui-option.cmake │ ├── gui.cmake │ ├── installer.cmake │ ├── libatomic.cmake │ ├── macos.cmake │ ├── ngtcp2_lib.cmake │ ├── solaris.cmake │ ├── target_link_libraries_system.cmake │ ├── unix.cmake │ ├── win32.cmake │ └── win32_installer_deps.cmake ├── contrib/ │ ├── NetworkManager/ │ │ └── dnsmasq/ │ │ ├── README.md │ │ └── lokinet.conf │ ├── android-configure.sh │ ├── android.sh │ ├── apparmor/ │ │ └── usr.bin.lokinet │ ├── apply-patches.sh │ ├── bencode-dump.py │ ├── bootstrap/ │ │ ├── mainnet.signed │ │ ├── make-bootstrap-list.sh │ │ ├── readme.txt │ │ └── testnet.signed │ ├── ci/ │ │ ├── docker/ │ │ │ ├── readme.md │ │ │ └── rebuild-docker-images.py │ │ ├── drone-check-static-libs.sh │ │ ├── drone-debs-upload.sh │ │ ├── drone-format-verify.sh │ │ ├── drone-gdb.sh │ │ ├── drone-run-router-hive.sh │ │ ├── drone-static-upload.sh │ │ └── gdb-filter.py │ ├── cross/ │ │ ├── android.toolchain.cmake │ │ ├── cross.toolchain.cmake │ │ ├── mingw32.cmake │ │ ├── mingw64.cmake │ │ └── mingw_core.cmake │ ├── cross.sh │ ├── deb.oxen.io.gpg │ ├── format-version.sh │ ├── format.sh │ ├── git-hook-pre-push.sh │ ├── hex-to-base32z.py │ ├── keygen.py │ ├── liblokinet/ │ │ ├── CMakeLists.txt │ │ ├── readme.md │ │ └── udptest.cpp │ ├── liblokinet_jank_test.cpp │ ├── lokinet-resolvconf │ ├── mac-configure.sh │ ├── mac.sh │ ├── macos/ │ │ ├── InfoPlist.strings │ │ ├── installer.tiff │ │ ├── lokinet-extension.Info.plist.in │ │ ├── lokinet-extension.dev.provisionprofile │ │ ├── lokinet-extension.plugin.entitlements.plist │ │ ├── lokinet-extension.release.provisionprofile │ │ ├── lokinet-extension.sysext.entitlements.plist │ │ ├── lokinet-newsyslog.conf │ │ ├── lokinet.Info.plist.in │ │ ├── lokinet.dev.provisionprofile │ │ ├── lokinet.plugin.entitlements.plist │ │ ├── lokinet.release.provisionprofile │ │ ├── lokinet.sysext.entitlements.plist │ │ ├── mk-icns.sh │ │ ├── notarize.py.in │ │ ├── seticon.swift │ │ └── sign.sh.in │ ├── make-ico.sh │ ├── omq-rpc.py │ ├── patches/ │ │ ├── libzmq-mingw-unistd.patch │ │ ├── libzmq-mingw-wepoll.patch │ │ └── unbound-delete-crash-fix.patch │ ├── py/ │ │ ├── .gitignore │ │ ├── admin/ │ │ │ ├── .gitignore │ │ │ ├── lokinetmon │ │ │ └── requirements.txt │ │ ├── ffi-example/ │ │ │ └── lokinet.py │ │ ├── keygen/ │ │ │ ├── .gitignore │ │ │ ├── keygen.py │ │ │ └── readme.md │ │ ├── lnproxy/ │ │ │ ├── lnproxy/ │ │ │ │ └── __main__.py │ │ │ └── readme.md │ │ └── quic_tester.py │ ├── readme-installer.txt │ ├── systemd-resolved/ │ │ ├── README.md │ │ ├── lokinet.pkla │ │ └── lokinet.rules │ ├── tarball.sh │ ├── windows-configure.sh │ └── windows.sh ├── daemon/ │ ├── CMakeLists.txt │ ├── lokinet-cntrl.cpp │ ├── lokinet.cpp │ ├── lokinet.swift │ ├── utils.cpp │ └── utils.hpp ├── docs/ │ ├── CMakeLists.txt │ ├── Doxyfile.in │ ├── LICENSE │ ├── architecture.md │ ├── config.json │ ├── dns-overview.md │ ├── doxygen.md │ ├── exit-setup.md │ ├── fix-markdown.sh │ ├── ideal-ux.md │ ├── index.md.in │ ├── install.md │ ├── liblokinet-dev-guide.md │ ├── macos-signing.txt │ ├── mkdocs.yml │ ├── net-comparisons.md │ ├── project-structure.md │ ├── readme.md │ ├── refactor_notes.md │ ├── snapps-dev-guide.md │ ├── spanish/ │ │ ├── LICENSE │ │ ├── README │ │ └── vision-general.txt │ ├── tcp-over-quic.md │ └── we-cannot-make-sandwiches.md ├── external/ │ └── CMakeLists.txt ├── include/ │ ├── llarp.hpp │ ├── lokinet/ │ │ ├── addr.h │ │ ├── context.h │ │ ├── export.h │ │ ├── misc.h │ │ ├── srv.h │ │ ├── stream.h │ │ └── udp.h │ ├── lokinet.h │ └── lokinet.hpp ├── jni/ │ ├── CMakeLists.txt │ ├── java/ │ │ └── src/ │ │ └── network/ │ │ └── loki/ │ │ └── lokinet/ │ │ ├── LokinetConfig.java │ │ └── LokinetDaemon.java │ ├── lokinet_config.cpp │ ├── lokinet_daemon.cpp │ ├── lokinet_jni_common.hpp │ ├── network_loki_lokinet_LokinetConfig.h │ ├── network_loki_lokinet_LokinetDaemon.h │ ├── network_loki_lokinet_LokinetVPN.h │ ├── network_loki_lokinet_LokinetVPN_VPNInfo.h │ ├── network_loki_lokinet_Lokinet_JNI.h │ └── readme.md ├── llarp/ │ ├── CMakeLists.txt │ ├── address/ │ │ ├── address.cpp │ │ ├── address.hpp │ │ ├── ip_range.cpp │ │ ├── ip_range.hpp │ │ ├── map.hpp │ │ ├── types.hpp │ │ ├── utils.cpp │ │ └── utils.hpp │ ├── android/ │ │ ├── ifaddrs.c │ │ └── ifaddrs.h │ ├── app.xml │ ├── apple/ │ │ ├── CMakeLists.txt │ │ ├── DNSTrampoline.h │ │ ├── DNSTrampoline.m │ │ ├── PacketTunnelProvider.m │ │ ├── context.hpp │ │ ├── context_wrapper.cpp │ │ ├── context_wrapper.h │ │ ├── route_manager.cpp │ │ ├── route_manager.hpp │ │ ├── vpn_interface.cpp │ │ ├── vpn_interface.hpp │ │ ├── vpn_platform.cpp │ │ └── vpn_platform.hpp │ ├── auth/ │ │ ├── auth.cpp │ │ ├── auth.hpp │ │ ├── file.cpp │ │ ├── file.hpp │ │ ├── rpc.cpp │ │ ├── rpc.hpp │ │ ├── session.cpp │ │ └── session.hpp │ ├── config/ │ │ ├── config.cpp │ │ ├── config.hpp │ │ ├── definition.cpp │ │ ├── definition.hpp │ │ ├── ini.cpp │ │ └── ini.hpp │ ├── consensus/ │ │ ├── reachability_testing.cpp │ │ └── reachability_testing.hpp │ ├── constants/ │ │ ├── apple.hpp │ │ ├── files.hpp │ │ ├── link_layer.hpp │ │ ├── net.hpp │ │ ├── path.hpp │ │ ├── platform.hpp │ │ ├── proto.hpp │ │ ├── version.cpp.in │ │ └── version.hpp │ ├── contact/ │ │ ├── client_contact.cpp │ │ ├── client_contact.hpp │ │ ├── client_intro.cpp │ │ ├── client_intro.hpp │ │ ├── contactdb.cpp │ │ ├── contactdb.hpp │ │ ├── relay_contact.cpp │ │ ├── relay_contact.hpp │ │ ├── router_id.cpp │ │ ├── router_id.hpp │ │ ├── sns.cpp │ │ └── sns.hpp │ ├── context.cpp │ ├── crypto/ │ │ ├── constants.hpp │ │ ├── crypto.cpp │ │ ├── crypto.hpp │ │ ├── key_manager.cpp │ │ ├── key_manager.hpp │ │ ├── keys.cpp │ │ ├── keys.hpp │ │ ├── types.cpp │ │ └── types.hpp │ ├── dns/ │ │ ├── dns.hpp │ │ ├── message.cpp │ │ ├── message.hpp │ │ ├── name.cpp │ │ ├── name.hpp │ │ ├── nm_platform.cpp │ │ ├── nm_platform.hpp │ │ ├── platform.cpp │ │ ├── platform.hpp │ │ ├── question.cpp │ │ ├── question.hpp │ │ ├── rr.cpp │ │ ├── rr.hpp │ │ ├── sd_platform.cpp │ │ ├── sd_platform.hpp │ │ ├── serialize.cpp │ │ ├── serialize.hpp │ │ ├── server.cpp │ │ ├── server.hpp │ │ ├── srv_data.cpp │ │ ├── srv_data.hpp │ │ └── string.hpp │ ├── ev/ │ │ ├── fd_poller.cpp │ │ ├── fd_poller.hpp │ │ ├── tcp.cpp │ │ ├── tcp.hpp │ │ ├── udp.cpp │ │ └── udp.hpp │ ├── handlers/ │ │ ├── session.cpp │ │ ├── session.hpp │ │ ├── tun.cpp │ │ ├── tun.hpp │ │ └── tun_base.hpp │ ├── link/ │ │ ├── connection.cpp │ │ ├── connection.hpp │ │ ├── endpoint.cpp │ │ ├── endpoint.hpp │ │ ├── link_manager.cpp │ │ └── link_manager.hpp │ ├── linux/ │ │ ├── dbus.cpp │ │ ├── dbus.hpp │ │ └── sd_service_manager.cpp │ ├── lokinet.cpp │ ├── lokinet_shared.cpp │ ├── messages/ │ │ ├── common.cpp │ │ ├── common.hpp │ │ ├── dht.cpp │ │ ├── dht.hpp │ │ ├── fetch.cpp │ │ ├── fetch.hpp │ │ ├── path.cpp │ │ ├── path.hpp │ │ ├── session.cpp │ │ └── session.hpp │ ├── net/ │ │ ├── id.hpp │ │ ├── ip_headers.hpp │ │ ├── ip_packet.cpp │ │ ├── ip_packet.hpp │ │ ├── net.h │ │ ├── net_if.hpp │ │ ├── platform.hpp │ │ ├── policy.cpp │ │ ├── policy.hpp │ │ ├── posix.cpp │ │ ├── utils.cpp │ │ ├── utils.hpp │ │ └── win32.cpp │ ├── nodedb-bootstraps.cpp.in │ ├── nodedb.cpp │ ├── nodedb.hpp │ ├── path/ │ │ ├── build_stats.cpp │ │ ├── build_stats.hpp │ │ ├── hopid.cpp │ │ ├── hopid.hpp │ │ ├── path.cpp │ │ ├── path.hpp │ │ ├── path_context.cpp │ │ ├── path_context.hpp │ │ ├── path_handler.cpp │ │ ├── path_handler.hpp │ │ ├── transit_hop.cpp │ │ └── transit_hop.hpp │ ├── profiling.cpp │ ├── profiling.hpp │ ├── router/ │ │ ├── route_poker.cpp │ │ ├── route_poker.hpp │ │ ├── router.cpp │ │ └── router.hpp │ ├── rpc/ │ │ ├── json_binary_proxy.cpp │ │ ├── json_binary_proxy.hpp │ │ ├── json_bt.hpp │ │ ├── json_conversions.cpp │ │ ├── json_conversions.hpp │ │ ├── oxend_rpc.cpp │ │ ├── oxend_rpc.hpp │ │ ├── param_parser.hpp │ │ ├── rpc_request.hpp │ │ ├── rpc_request_decorators.hpp │ │ ├── rpc_request_definitions.hpp │ │ ├── rpc_request_parser.cpp │ │ ├── rpc_request_parser.hpp │ │ ├── rpc_server.cpp │ │ └── rpc_server.hpp │ ├── session/ │ │ ├── session.cpp │ │ └── session.hpp │ ├── simulation/ │ │ ├── sim_context.cpp │ │ └── sim_context.hpp │ ├── util/ │ │ ├── aligned.hpp │ │ ├── bspan.hpp │ │ ├── buffer.cpp │ │ ├── buffer.hpp │ │ ├── common.hpp │ │ ├── compare_ptr.hpp │ │ ├── decaying_hashset.hpp │ │ ├── decaying_hashtable.hpp │ │ ├── exceptions.hpp │ │ ├── file.cpp │ │ ├── file.hpp │ │ ├── formattable.hpp │ │ ├── logging/ │ │ │ ├── buffer.hpp │ │ │ └── callback_sink.hpp │ │ ├── logging.cpp │ │ ├── logging.hpp │ │ ├── lokinet_init.h │ │ ├── mem.cpp │ │ ├── mem.h │ │ ├── mem.hpp │ │ ├── nop_service_manager.cpp │ │ ├── random.hpp │ │ ├── service_manager.hpp │ │ ├── str.cpp │ │ ├── str.hpp │ │ ├── thread/ │ │ │ ├── barrier.hpp │ │ │ ├── queue.hpp │ │ │ ├── queue_manager.cpp │ │ │ ├── queue_manager.hpp │ │ │ ├── threading.cpp │ │ │ └── threading.hpp │ │ ├── time.cpp │ │ ├── time.hpp │ │ ├── zstd.cpp │ │ └── zstd.hpp │ ├── vpn/ │ │ ├── android.hpp │ │ ├── common.hpp │ │ ├── egres_packet_router.cpp │ │ ├── egres_packet_router.hpp │ │ ├── linux.hpp │ │ ├── packet_intercept.hpp │ │ ├── packet_io.hpp │ │ ├── packet_router.cpp │ │ ├── packet_router.hpp │ │ ├── platform.cpp │ │ ├── platform.hpp │ │ ├── win32.cpp │ │ └── win32.hpp │ └── win32/ │ ├── adapters.hpp │ ├── dll.cpp │ ├── dll.hpp │ ├── exception.cpp │ ├── exception.hpp │ ├── exec.cpp │ ├── exec.hpp │ ├── guid.hpp │ ├── handle.hpp │ ├── service_manager.cpp │ ├── service_manager.hpp │ ├── version.rc.in │ ├── win32_inet.c │ ├── win32_intrnl.c │ ├── windivert.cpp │ ├── windivert.hpp │ ├── wintun.cpp │ └── wintun.hpp ├── pybind/ │ ├── CMakeLists.txt │ ├── common.hpp │ ├── llarp/ │ │ ├── config.cpp │ │ ├── context.cpp │ │ ├── crypto/ │ │ │ └── types.cpp │ │ ├── dht/ │ │ │ └── dht_types.cpp │ │ ├── handlers/ │ │ │ ├── pyhandler.cpp │ │ │ └── pyhandler.hpp │ │ ├── logger.cpp │ │ ├── path/ │ │ │ ├── path_hop_config.cpp │ │ │ └── path_types.cpp │ │ ├── peerstats.cpp │ │ ├── router.cpp │ │ ├── router_contact.cpp │ │ ├── router_id.cpp │ │ ├── service/ │ │ │ └── address.cpp │ │ └── tooling/ │ │ ├── router_event.cpp │ │ └── router_hive.cpp │ ├── module.cpp │ └── readme.md ├── readme.md ├── readme_es.md ├── readme_fr.md ├── readme_ru.md ├── test/ │ ├── CMakeLists.txt │ ├── check_main.cpp │ ├── config/ │ │ ├── test_llarp_config_definition.cpp │ │ ├── test_llarp_config_ini.cpp │ │ ├── test_llarp_config_output.cpp │ │ └── test_llarp_config_values.cpp │ ├── crypto/ │ │ ├── test_llarp_crypto.cpp │ │ ├── test_llarp_crypto_types.cpp │ │ └── test_llarp_key_manager.cpp │ ├── dns/ │ │ └── test_llarp_dns_dns.cpp │ ├── hive/ │ │ ├── conftest.py │ │ ├── hive.py │ │ ├── test_path_builds.py │ │ └── test_peer_stats.py │ ├── mocks/ │ │ ├── mock_context.hpp │ │ ├── mock_network.hpp │ │ ├── mock_router.hpp │ │ └── mock_vpn.hpp │ ├── net/ │ │ ├── test_ip_address.cpp │ │ ├── test_llarp_net.cpp │ │ └── test_sock_addr.cpp │ ├── nodedb/ │ │ └── test_nodedb.cpp │ ├── path/ │ │ └── test_path.cpp │ ├── peerstats/ │ │ ├── test_peer_db.cpp │ │ └── test_peer_types.cpp │ ├── readme.md │ ├── router/ │ │ └── test_llarp_router_version.cpp │ ├── routing/ │ │ ├── test_llarp_routing_obtainexitmessage.cpp │ │ └── test_llarp_routing_transfer_traffic.cpp │ ├── service/ │ │ ├── test_llarp_service_address.cpp │ │ ├── test_llarp_service_identity.cpp │ │ └── test_llarp_service_name.cpp │ ├── test_llarp_encrypted_frame.cpp │ ├── test_llarp_router_contact.cpp │ ├── test_util.cpp │ ├── test_util.hpp │ ├── util/ │ │ ├── meta/ │ │ │ └── test_llarp_util_memfn.cpp │ │ ├── test_llarp_util_aligned.cpp │ │ ├── test_llarp_util_bencode.cpp │ │ ├── test_llarp_util_bits.cpp │ │ ├── test_llarp_util_decaying_hashset.cpp │ │ ├── test_llarp_util_log_level.cpp │ │ ├── test_llarp_util_str.cpp │ │ └── thread/ │ │ ├── test_llarp_util_queue.cpp │ │ └── test_llarp_util_queue_manager.cpp │ └── win32/ │ └── test.rc └── win32-setup/ ├── .gitignore ├── Makefile ├── config_migration.bat ├── curl-win32.patch ├── extra_create_icons.nsis ├── extra_delete_icons.nsis ├── extra_install.nsis ├── extra_preinstall.nsis ├── extra_uninstall.nsis ├── libsodium-1.0.17-win32.patch ├── libsodium-1.0.18-win32.patch ├── lokinet-win32.iss ├── mbedtls-win32.patch ├── notes.txt └── regdb_helper.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: Google # alignment AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: 'false' AlignConsecutiveDeclarations: 'false' AlignEscapedNewlinesLeft: 'true' AlignOperands: 'false' AlignTrailingComments: 'true' ColumnLimit: 120 PointerAlignment: Left QualifierAlignment: Custom QualifierOrder: ['inline', 'static', 'constexpr', 'const', 'type'] ReferenceAlignment: Left # bracing BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: true AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: true BeforeElse: true SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false # breaking AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: 'true' BreakBeforeBinaryOperators: NonAssignment BreakBeforeTernaryOperators: 'true' BreakConstructorInitializers: BeforeColon # indentation AccessModifierOffset: -2 ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 IndentWidth: 4 NamespaceIndentation: All # shorties AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: 'false' AllowShortLoopsOnASingleLine: 'false' # spacing KeepEmptyLinesAtTheStartOfBlocks: 'false' PenaltyBreakString: '3' SpaceBeforeParens: ControlStatements SpacesInAngles: 'false' SpacesInContainerLiterals: 'false' SpacesInParentheses: 'false' SpacesInSquareBrackets: 'false' Standard: c++20 UseTab: Never # wrapping PackConstructorInitializers: NextLine BinPackParameters: 'false' BinPackArguments: 'false' # Include block sorting in the following order: # - Main header for source file (clang-format default prioritizes this first) # - Relative path includes in quotation marks # - Absolute path includes in angle brackets # - External dependencies # - System dependencies SortIncludes: CaseInsensitive IncludeBlocks: Regroup IncludeCategories: - Regex: '".+\.h' Priority: 2 - Regex: '^' Priority: 4 - Regex: '' Priority: 5 - Regex: '^<.*\.h(pp)?>$' Priority: 6 - Regex: '(<)(.)+(>)' Priority: 7 ================================================ FILE: .clang-tidy ================================================ HeaderFilterRegex: 'llarp/.*' Checks: 'readability-else-after-return, clang-analyzer-core-*,modernize-*, -modernize-use-trailing-return-type, -modernize-use-nodiscard, bugprone-*, -bugprone-easily-swappable-parameters' ================================================ FILE: .dir-locals.el ================================================ ((c++-mode (eval add-hook 'before-save-hook #'clang-format-buffer nil t)) (c-mode (eval add-hook 'before-save-hook #'clang-format-buffer nil t))) ================================================ FILE: .dockerignore ================================================ build/ .vscode/ lokinet lokinet.exe ================================================ FILE: .drone.jsonnet ================================================ local default_deps_base = std.set([ 'g++', 'libcli11-dev', 'libcurl4-openssl-dev', 'libevent-dev', 'libfmt-dev', 'libgnutls28-dev', 'libsodium-dev', 'libspdlog-dev', 'libsqlite3-dev', 'libssl-dev', 'libsystemd-dev', 'libunbound-dev', 'libzmq3-dev', 'libzstd-dev', 'make', 'nettle-dev', 'nlohmann-json3-dev', 'python3-dev', ]); local default_deps(add=[], remove=[]) = std.setDiff( std.setUnion(default_deps_base, if std.isArray(add) then std.set(add) else [add]), std.set(if std.isArray(remove) then std.set(remove) else [remove]) ); local static_deps = std.set(['g++', 'python3-dev', 'automake', 'libtool']); local oxen_repo_default = ['liboxen-logging-dev', 'liboxenmq-dev', 'liboxenc-dev', 'liboxen-quic-dev']; local docker_base = 'registry.oxen.rocks/'; local submodule_commands = [ 'git fetch --tags', 'git submodule update --init --recursive --depth=1 --jobs=4', ]; local submodules = { name: 'submodules', image: 'drone/git', commands: submodule_commands, }; // cmake options for static deps mirror local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); local apt_get_quiet = 'apt-get -o=Dpkg::Use-Pty=0 -q'; local kitware_repo(distro) = [ 'eatmydata ' + apt_get_quiet + ' install -y curl ca-certificates', 'curl -sSL https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - >/usr/share/keyrings/kitware-archive-keyring.gpg', 'echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ ' + distro + ' main" >/etc/apt/sources.list.d/kitware.list', 'eatmydata ' + apt_get_quiet + ' update', ]; local debian_backports(distro, pkgs) = [ 'echo "deb http://deb.debian.org/debian ' + distro + '-backports main" >/etc/apt/sources.list.d/' + distro + '-backports.list', 'eatmydata ' + apt_get_quiet + ' update', 'eatmydata ' + apt_get_quiet + ' install -y ' + std.join(' ', std.map(function(x) x + '/' + distro + '-backports', pkgs)), ]; // Regular build on a debian-like system: local debian_pipeline(name, image, arch='amd64', deps=default_deps(), extra_setup=[], build_type='Release', lto=false, werror=true, cmake_extra='', local_mirror=true, extra_cmds=[], jobs=6, tests=false, // FIXME TODO: temporary until test suite is fixed oxen_repo=oxen_repo_default, allow_fail=false) = { kind: 'pipeline', type: 'docker', name: name, platform: { arch: arch }, trigger: { branch: { exclude: ['debian/*', 'ubuntu/*'] } }, steps: [ submodules, { name: 'build', image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', ] + ( if std.length(oxen_repo) > 0 then [ 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y lsb-release', 'cp contrib/deb.oxen.io.gpg /etc/apt/trusted.gpg.d', 'echo deb http://deb.oxen.io $$(lsb_release -sc) main >/etc/apt/sources.list.d/oxen.list', 'eatmydata ' + apt_get_quiet + ' update', apt_get_quiet + ' install -y ' + std.join(' ', oxen_repo), ] else [] ) + extra_setup + [ 'eatmydata ' + apt_get_quiet + ' dist-upgrade -y', 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y gdb cmake git pkg-config ccache ' + std.join(' ', deps), 'mkdir build', 'cd build', 'cmake .. -DWITH_SETCAP=OFF -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_BUILD_TYPE=' + build_type + ' ' + '-DWARN_DEPRECATED=OFF ' + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + '-DWITH_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + cmake_extra + ci_dep_mirror(local_mirror), 'VERBOSE=1 make -j' + jobs, 'cd ..', ] + (if tests then ['./contrib/ci/drone-gdb.sh ./build/test/testAll --use-colour yes'] else []) + extra_cmds, }, ], }; local apk_builder(name, image, extra_cmds=[], allow_fail=false, jobs=6) = { kind: 'pipeline', type: 'docker', name: name, platform: { arch: 'amd64' }, trigger: { branch: { exclude: ['debian/*', 'ubuntu/*'] } }, steps: [ submodules, { name: 'build', image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, ANDROID: 'android' }, commands: [ 'VERBOSE=1 JOBS=' + jobs + ' NDK=/usr/lib/android-ndk ./contrib/android.sh', 'git clone https://github.com/oxen-io/lokinet-flutter-app lokinet-mobile', 'cp -av build-android/out/* lokinet-mobile/lokinet_lib/android/src/main/jniLibs/', 'cd lokinet-mobile', 'flutter build apk --debug', 'cd ..', 'cp lokinet-mobile/build/app/outputs/apk/debug/app-debug.apk lokinet.apk', ] + extra_cmds, }, ], }; // windows cross compile on debian local windows_cross_pipeline(name, image, gui_image=docker_base + 'nodejs-lts', arch='amd64', build_type='Release', lto=false, werror=false, cmake_extra='', local_mirror=true, extra_cmds=[], jobs=6, allow_fail=false) = { kind: 'pipeline', type: 'docker', name: name, platform: { arch: arch }, trigger: { branch: { exclude: ['debian/*', 'ubuntu/*'] } }, steps: [ submodules, { name: 'GUI', image: gui_image, pull: 'always', [if allow_fail then 'failure']: 'ignore', commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y p7zip-full wine', 'cd gui', 'yarn install --frozen-lockfile', 'USE_SYSTEM_7ZA=true DISPLAY= WINEDEBUG=-all yarn win32', ], }, { name: 'build', image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, WINDOWS_BUILD_NAME: 'x64' }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y build-essential cmake git pkg-config ccache g++-mingw-w64-x86-64-posix nsis zip icoutils automake libtool librsvg2-bin bison', 'JOBS=' + jobs + ' VERBOSE=1 ./contrib/windows.sh -DSTRIP_SYMBOLS=ON -DGUI_EXE=$${DRONE_WORKSPACE}/gui/release/Lokinet-GUI_portable.exe' + ci_dep_mirror(local_mirror), ] + extra_cmds, }, ], }; // linux cross compile on debian local linux_cross_pipeline(name, cross_targets, arch='amd64', build_type='Release', cmake_extra='', local_mirror=true, extra_cmds=[], jobs=6, allow_fail=false) = { kind: 'pipeline', type: 'docker', name: name, platform: { arch: arch }, trigger: { branch: { exclude: ['debian/*', 'ubuntu/*'] } }, steps: [ submodules, { name: 'build', image: docker_base + 'debian-stable-cross', pull: 'always', [if allow_fail then 'failure']: 'ignore', environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, CROSS_TARGETS: std.join(':', cross_targets) }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'VERBOSE=1 JOBS=' + jobs + ' ./contrib/cross.sh ' + std.join(' ', cross_targets) + ' -- ' + cmake_extra + ci_dep_mirror(local_mirror), ], }, ], }; // Builds a snapshot .deb on a debian-like system by merging into the debian/* or ubuntu/* branch local deb_builder(image, distro, distro_branch, arch='amd64', oxen_repo=oxen_repo_default) = { kind: 'pipeline', type: 'docker', name: 'DEB (' + distro + (if arch == 'amd64' then '' else '/' + arch) + ')', platform: { arch: arch }, environment: { distro_branch: distro_branch, distro: distro }, steps: [ submodules, { name: 'build', image: image, pull: 'always', failure: 'ignore', environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', ] + (if oxen_repo then [ 'cp contrib/deb.oxen.io.gpg /etc/apt/trusted.gpg.d', 'echo deb http://deb.oxen.io $${distro} main >/etc/apt/sources.list.d/oxen.list', ] else []) + [ apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git devscripts equivs ccache git-buildpackage python3-dev', ||| # Look for the debian branch in this repo first, try upstream if that fails. if ! git checkout $${distro_branch}; then git remote add --fetch upstream https://github.com/oxen-io/lokinet.git && git checkout $${distro_branch} fi |||, // Tell the merge how to resolve conflicts in the source .drone.jsonnet (we don't // care about it at all since *this* .drone.jsonnet is already loaded). 'git config merge.ours.driver true', 'echo .drone.jsonnet merge=ours >>.gitattributes', 'git merge ${DRONE_COMMIT}', 'export DEBEMAIL="${DRONE_COMMIT_AUTHOR_EMAIL}" DEBFULLNAME="${DRONE_COMMIT_AUTHOR_NAME}"', 'gbp dch -S -s "HEAD^" --spawn-editor=never -U low', 'eatmydata mk-build-deps --install --remove --tool "' + apt_get_quiet + ' -o Debug::pkgProblemResolver=yes --no-install-recommends -y"', 'export DEB_BUILD_OPTIONS="parallel=$$(nproc)"', //'grep -q lib debian/lokinet-bin.install || echo "/usr/lib/lib*.so*" >>debian/lokinet-bin.install', 'debuild -e CCACHE_DIR -b', './contrib/ci/drone-debs-upload.sh ' + distro, ], }, ], }; local clang(version) = debian_pipeline( 'Debian sid/clang-' + version, docker_base + 'debian-sid-clang', deps=default_deps(add='clang-' + version, remove='g++'), cmake_extra='-DCMAKE_C_COMPILER=clang-' + version + ' -DCMAKE_CXX_COMPILER=clang++-' + version + ' ' ); local full_llvm(version) = debian_pipeline( 'Debian sid/llvm-' + version, docker_base + 'debian-sid-clang', deps=default_deps(add=['clang-' + version, ' lld-' + version, ' libc++-' + version + '-dev', 'libc++abi-' + version + '-dev', 'libngtcp2-crypto-gnutls-dev', 'libngtcp2-dev'], remove='g++'), oxen_repo=[], cmake_extra='-DCMAKE_C_COMPILER=clang-' + version + ' -DCMAKE_CXX_COMPILER=clang++-' + version + ' -DCMAKE_CXX_FLAGS=-stdlib=libc++ ' + std.join(' ', [ '-DCMAKE_' + type + '_LINKER_FLAGS=-fuse-ld=lld-' + version for type in ['EXE', 'MODULE', 'SHARED'] ]) + ' -DOXEN_LOGGING_FORCE_SUBMODULES=ON' ); // Macos build local mac_builder(name, build_type='Release', arch='arm64', werror=true, cmake_extra='', local_mirror=true, extra_cmds=[], jobs=6, codesign='-DCODESIGN=OFF', allow_fail=false) = { kind: 'pipeline', type: 'exec', name: name, platform: { os: 'darwin', arch: arch }, steps: [ { name: 'submodules', commands: submodule_commands }, { name: 'build', environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', // If you don't do this then the C compiler doesn't have an include path containing // basic system headers. WTF apple: 'export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"', 'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk './contrib/mac-configure.sh ' + ci_dep_mirror(local_mirror) + '-DWARN_DEPRECATED=OFF ' + codesign, 'cd build-mac', // We can't use the 'package' target here because making a .dmg requires an active logged in // macos gui to invoke Finder to invoke the partitioning tool to create a partitioned (!) // disk image. Most likely the GUI is required because if you lose sight of how pretty the // surface of macOS is you might see how ugly the insides are. 'ninja -j' + jobs + ' assemble_gui', 'cd ..', ] + extra_cmds, }, ], }; local docs_pipeline(name, image, extra_cmds=[], allow_fail=false) = { kind: 'pipeline', type: 'docker', name: name, platform: { arch: 'amd64' }, trigger: { branch: { exclude: ['debian/*', 'ubuntu/*'] } }, steps: [ submodules, { name: 'build', image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ 'cmake -S . -B build-docs', 'make -C build-docs doc', ] + extra_cmds, }, ], }; [ { name: 'lint check', kind: 'pipeline', type: 'docker', steps: [{ name: 'build', image: docker_base + 'lint', pull: 'always', commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-16 jsonnet', './contrib/ci/drone-format-verify.sh', ], }], }, // documentation builder //docs_pipeline('Documentation', // docker_base + 'docbuilder', // extra_cmds=['UPLOAD_OS=docs ./contrib/ci/drone-static-upload.sh']), // Debian sid debian_pipeline('Debian sid', docker_base + 'debian-sid'), debian_pipeline('Debian sid/debug', docker_base + 'debian-sid', build_type='Debug'), debian_pipeline('Debian sid/debug [arm64]', docker_base + 'debian-sid', build_type='Debug', arch='arm64', jobs=4), clang(17), full_llvm(17), clang(19), full_llvm(19), // Debian 14 debian_pipeline('Debian 14', docker_base + 'debian-forky'), debian_pipeline('Debian 14 [i386]', docker_base + 'debian-forky/i386'), debian_pipeline('Debian 14 [arm64]', docker_base + 'debian-forky', arch='arm64', jobs=4), debian_pipeline('Debian 14 [armhf]', docker_base + 'debian-forky/arm32v7', arch='arm64', jobs=4), // Debian 13 debian_pipeline('Debian 13', docker_base + 'debian-trixie'), debian_pipeline('Debian 13 [arm64]', docker_base + 'debian-trixie', arch='arm64', jobs=4), // Debian 12 debian_pipeline('Debian 12', docker_base + 'debian-bookworm'), debian_pipeline('Debian 12 static/debug', docker_base + 'debian-bookworm', build_type='Debug', deps=static_deps, oxen_repo=[], cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON'), // Static debian 12 armhf (upload to builds.lokinet.dev) debian_pipeline('Debian 12 static [armhf]', docker_base + 'debian-bookworm/arm32v7', arch='arm64', deps=static_deps, oxen_repo=[], cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + '-DCMAKE_CXX_FLAGS="-march=armv7-a+fp -Wno-psabi" -DCMAKE_C_FLAGS="-march=armv7-a+fp" ' + '-DNATIVE_BUILD=OFF -DWITH_SYSTEMD=OFF -DWITH_BOOTSTRAP=OFF', extra_cmds=[ './contrib/ci/drone-check-static-libs.sh', 'UPLOAD_OS=linux-armhf ./contrib/ci/drone-static-upload.sh', ], jobs=4), // Ubuntu debian_pipeline('Ubuntu latest', docker_base + 'ubuntu-rolling'), debian_pipeline('Ubuntu 24.04', docker_base + 'ubuntu-noble'), debian_pipeline('Ubuntu 22.04', docker_base + 'ubuntu-jammy'), // Static ubuntu jammy amd64 build (upload to builds.lokinet.dev) debian_pipeline('Ubuntu 22.04 static', docker_base + 'ubuntu-jammy', deps=static_deps, lto=true, tests=false, oxen_repo=[], cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON ' + '-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" ' + '-DCMAKE_C_FLAGS="-march=x86-64 -mtune=haswell" ' + '-DNATIVE_BUILD=OFF -DWITH_SYSTEMD=OFF -DWITH_BOOTSTRAP=OFF -DBUILD_LIBLOKINET=OFF', extra_cmds=[ './contrib/ci/drone-check-static-libs.sh', './contrib/ci/drone-static-upload.sh', ]), // cross compile targets // Aug 11: these are exhibiting some dumb failures in libsodium and external deps, TOFIX later //linux_cross_pipeline('Cross Compile (arm/arm64)', cross_targets=['arm-linux-gnueabihf', 'aarch64-linux-gnu']), //linux_cross_pipeline('Cross Compile (ppc64le)', cross_targets=['powerpc64le-linux-gnu']), // Not currently building successfully: //linux_cross_pipeline('Cross Compile (mips)', cross_targets=['mips-linux-gnu', 'mipsel-linux-gnu']), // android apk builder // Aug 11: this is also failing in openssl, TOFIX later //apk_builder('android apk', docker_base + 'flutter', extra_cmds=['UPLOAD_OS=android ./contrib/ci/drone-static-upload.sh']), // Windows builds (x64) /* windows_cross_pipeline('Windows (x64)', docker_base + 'debian-win32-cross', extra_cmds=[ './contrib/ci/drone-static-upload.sh', ]), */ /* // integration tests debian_pipeline('Router Hive', docker_base + 'ubuntu-lts', deps=default_deps(add=['python3-dev', 'python3-pytest', 'python3-pybind11']), cmake_extra='-DWITH_HIVE=ON'), // Deb builds: deb_builder(docker_base + 'debian-sid-builder', 'sid', 'debian/sid'), deb_builder(docker_base + 'debian-bullseye-builder', 'bullseye', 'debian/bullseye'), deb_builder(docker_base + 'ubuntu-jammy-builder', 'jammy', 'ubuntu/jammy'), deb_builder(docker_base + 'debian-sid-builder', 'sid', 'debian/sid', arch='arm64'), */ // Macos builds: /* mac_builder('macOS (Release, arm64)', extra_cmds=[ './contrib/ci/drone-check-static-libs.sh', './contrib/ci/drone-static-upload.sh', ]), mac_builder('macOS (Debug, arm64)', build_type='Debug'), */ ] ================================================ FILE: .gitattributes ================================================ external/date/test export-ignore external/nlohmann/doc export-ignore external/nlohmann/test export-ignore external/nlohmann/benchmarks/data export-ignore *.signed binary ================================================ FILE: .github/CONTRIBUTING.md ================================================ * RUN `make format && make lint -j8` BEFORE COMMITING ALWAYS. * no tabs ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Screenshots Or Logs** If applicable, add screenshots or log files to help explain your problem. **Device and Operating system (please complete the following information):** - OS: [e.g. iOS, Windows, Android] - Device: [ e.g. Mac, PC, IPhone] - Lokinet Version number or Git commit hash: ================================================ FILE: .github/workflows/clean_issues.yml ================================================ name: Close incomplete issues on: schedule: - cron: "30 1 * * *" jobs: close-issues: runs-on: ubuntu-latest permissions: issues: write steps: - uses: actions/stale@v4.1.1 with: only-labels: incomplete days-before-issue-stale: 14 days-before-issue-close: 7 stale-issue-label: "stale" stale-issue-message: "This issue is stale because it has been 'incomplete' for 14 days with no activity." close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale." days-before-pr-stale: -1 days-before-pr-close: -1 repo-token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ *~ *\#* *.a *.o *.so /build*/ **/__pycache__/** llarpd *.test *.bin *.ninja cmake_install.cmake CMakeFiles CMakeCache.txt .ninja_log .ninja_deps /.cache/ /compile_commands.json callgrind.* .gdb_history *.sig *.signed !/contrib/bootstrap/mainnet.signed !/contrib/bootstrap/testnet.signed *.key shadow.data shadow.config.xml *.log *.pdf *.xz testnet_tmp *.pid vsproject/ .vs *.ini .gradle/ .idea .vscode build64/ build2/ /contrib/lokinet-bootstrap-winnt/cacert.pem /contrib/lokinet-bootstrap-winnt/data.enc /contrib/lokinet-bootstrap-winnt/out.bine default.profraw # ctags shit GTAGS GRTAGS GPATH version.txt lokinet-bootstrap.exe regdbhelper.dll # xcode xcuserdata/ scc.py ================================================ FILE: .gitmodules ================================================ [submodule "external/nlohmann"] path = external/nlohmann url = https://github.com/nlohmann/json.git [submodule "test/Catch2"] path = test/Catch2 url = https://github.com/catchorg/Catch2 [submodule "external/pybind11"] path = external/pybind11 url = https://github.com/pybind/pybind11 branch = stable [submodule "external/sqlite_orm"] path = external/sqlite_orm url = https://github.com/fnc12/sqlite_orm [submodule "external/oxen-mq"] path = external/oxen-mq url = https://github.com/oxen-io/oxen-mq [submodule "gui"] path = gui url = https://github.com/oxen-io/lokinet-gui.git [submodule "external/CLI11"] path = external/CLI11 url = https://github.com/CLIUtils/CLI11.git [submodule "external/oxen-libquic"] path = external/oxen-libquic url = https://github.com/oxen-io/oxen-libquic.git ================================================ FILE: .swift-version ================================================ 5.4.2 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.13...3.24) # 3.13 is buster's version # Cmake 3.24+ breaks extraction timestamps by default, hurray, but the option to not break # timestamps fails in cmake <3.24, extra hurray! if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) cmake_policy(SET CMP0135 OLD) endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Has to be set before `project()`, and ignored on non-macos: set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") set(lokinet_full_def ON) if(IOS) set(lokinet_full_def OFF) endif() # These two are not exclusive: daemon requires full platform, but the main lokinet library itself # can support both. option(LOKINET_FULL "build with full client/relay support. Disabling this will only allow embedded lokinet usage" ${lokinet_full_def}) option(LOKINET_DAEMON "build lokinet daemon and associated utils (requires LOKINET_FULL)" ${LOKINET_FULL}) if(LOKINET_DAEMON AND NOT LOKINET_FULL) message(FATAL_ERROR "Cannot use LOKINET_DAEMON without LOKINET_FULL platform support!") endif() option(LOKINET_GRAPH_DEPENDENCIES "Produce graphviz representation of cmake dependencies" OFF) option(LOKINET_VERSION_SO "Add the project version to the shared library filename, e.g. liblokinet1.2.3.so instead of liblokinet.so" OFF) set(LANGS C CXX) if(APPLE AND LOKINET_DAEMON) set(LANGS ${LANGS} OBJC Swift) endif() find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) foreach(lang ${LANGS}) if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER AND NOT CMAKE_${lang}_COMPILER MATCHES ".*/ccache") message(STATUS "Enabling ccache for ${lang}") set(CMAKE_${lang}_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE STRING "") endif() endforeach() endif() project(lokinet VERSION 0.10.0 DESCRIPTION "lokinet - IP packet onion router" LANGUAGES ${LANGS}) if(APPLE) # Apple build number: must be incremented to submit a new build for the same lokinet version, # should be reset to 0 when the lokinet version increments. set(LOKINET_APPLE_BUILD 5) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # Core options option(LOKINET_AVX2 "enable avx2 code" OFF) option(LOKINET_NATIVE_BUILD "optimise for host system and FPU" ON) option(LOKINET_XSAN "use sanitiser, if your system has it (requires -DCMAKE_BUILD_TYPE=Debug)" OFF) option(LOKINET_JEMALLOC "Link to jemalloc for memory allocations, if found (requires non-static build)" ON) option(LOKINET_COVERAGE "generate coverage data" OFF) option(LOKINET_WARNINGS_AS_ERRORS "treat all warnings as errors. turn off for development, on for release" OFF) option(LOKINET_TESTS "build unit tests" OFF) option(LOKINET_HIVE "build simulation stubs" OFF) option(LOKINET_PACKAGE "builds extra components for making an installer (with 'make package')" OFF) option(LOKINET_PEERSTATS "build with experimental peerstats db support" OFF) option(LOKINET_STRIP "strip off all debug symbols into an external archive for all executables built" OFF) option(LOKINET_DEBUG_PATH_SEED "enable support for the debug-mode [paths]:debug-path-seed option for reproducible, non-random paths" OFF) set(LOKINET_BOOTSTRAP_FALLBACK_MAINNET "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed" CACHE PATH "Fallback bootstrap path (mainnet)") set(LOKINET_BOOTSTRAP_FALLBACK_TESTNET "${PROJECT_SOURCE_DIR}/contrib/bootstrap/testnet.signed" CACHE PATH "Fallback bootstrap path (testnet)") include(cmake/enable_lto.cmake) option(CROSS_PLATFORM "cross compiler platform" "Linux") option(CROSS_PREFIX "toolchain cross compiler prefix" "") option(BUILD_SHARED_LIBS "Build shared library" OFF) option(BUILD_STATIC_DEPS "Download, build, and statically link against core dependencies" OFF) option(STATIC_LINK "link statically against dependencies" ${BUILD_STATIC_DEPS}) if(BUILD_STATIC_DEPS AND NOT STATIC_LINK) message(FATAL_ERROR "Option BUILD_STATIC_DEPS requires STATIC_LINK to be enabled as well") endif() if(BUILD_STATIC_DEPS AND BUILD_SHARED_LIBS) message(FATAL_ERROR "Incompatible options: BUILD_STATIC_DEPS cannot be used with BUILD_SHARED_LIBS") endif() if(BUILD_STATIC_DEPS) set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) include(cmake/StaticBuild.cmake) endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() # set(debug OFF) # if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]") # set(debug ON) # add_definitions(-DLOKINET_DEBUG) # endif() option(LOKINET_WARN_DEPRECATED "show deprecation warnings" OFF) include(CheckCXXSourceCompiles) include(CheckLibraryExists) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) include(cmake/target_link_libraries_system.cmake) include(cmake/add_import_library.cmake) include(cmake/libatomic.cmake) if (STATIC_LINK) set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) message(STATUS "setting static library suffix search") endif() include(cmake/gui-option.cmake) include(cmake/solaris.cmake) include(cmake/win32.cmake) include(cmake/macos.cmake) # No in-source building include(MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out-of-source build. Create a build directory and run 'cmake ${CMAKE_SOURCE_DIR} [options]'.") # Always build PIC set(CMAKE_POSITION_INDEPENDENT_CODE ON) include(cmake/unix.cmake) if(NOT WIN32) if(IOS OR ANDROID) set(NON_PC_TARGET ON) else() include(TargetArch) target_architecture(COMPILE_ARCH) if(COMPILE_ARCH MATCHES i386 OR COMPILE_ARCH MATCHES x86_64) set(NON_PC_TARGET OFF) else() set(NON_PC_TARGET ON) endif() endif() endif() find_package(PkgConfig REQUIRED) if(NOT TARGET sodium) # Allow -DLOKINET_DOWNLOAD_SODIUM=FORCE to download without even checking for a local libsodium option(LOKINET_DOWNLOAD_SODIUM "Allow libsodium to be downloaded and built locally if not found on the system" OFF) if(NOT LOKINET_DOWNLOAD_SODIUM STREQUAL "FORCE" AND NOT BUILD_STATIC_DEPS) pkg_check_modules(SODIUM libsodium>=1.0.18 IMPORTED_TARGET) endif() add_library(sodium INTERFACE) if(SODIUM_FOUND AND NOT LOKINET_DOWNLOAD_SODIUM STREQUAL "FORCE" AND NOT BUILD_STATIC_DEPS) target_link_libraries(sodium INTERFACE PkgConfig::SODIUM) else() if(NOT LOKINET_DOWNLOAD_SODIUM AND NOT BUILD_STATIC_DEPS) message(FATAL_ERROR "Could not find libsodium >= 1.0.18; either install it on your system or use -DLOKINET_DOWNLOAD_SODIUM=ON to download and build an internal copy") endif() message(STATUS "Sodium >= 1.0.18 not found, but LOKINET_DOWNLOAD_SODIUM specified, so downloading it") include(DownloadLibSodium) target_link_libraries(sodium INTERFACE sodium_vendor) endif() # Need this target export so that loki-mq properly picks up sodium export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake) endif() set(warning_flags -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Werror=vla) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") list(APPEND warning_flags -Wno-unknown-warning-option) endif() if(LOKINET_WARNINGS_AS_ERRORS) list(APPEND warning_flags -Werror -Wno-error=array-bounds) endif() # GCC 12's stringop-overflow warnings are really broken, with tons and tons of false positives all # over the place (not just in our code, but also in its own stdlibc++ code). It's better in 13 # w.r.t. stdlibc++, but our code still throws tons of warnings, so disable it for now. if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) list(APPEND warning_flags -Wno-stringop-overflow) endif() list(APPEND warning_flags -Wno-deprecated-declarations) add_library(lokinet-base-internal_warnings INTERFACE) # If we blindly add these directly as compile_options then they get passed to swiftc on Apple and # break, so we use a generate expression to set them only for C++/C/ObjC target_compile_options(lokinet-base-internal_warnings INTERFACE "$<$,$,$>:${warning_flags}>") if(LOKINET_XSAN) string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fsanitize=${LOKINET_XSAN} -fno-omit-frame-pointer -fno-sanitize-recover") foreach(type EXE MODULE SHARED STATIC) string(APPEND CMAKE_${type}_LINKER_FLAGS_DEBUG " -fsanitize=${LOKINET_XSAN} -fno-omit-frame-pointer -fno-sanitize-recover") endforeach() message(STATUS "Doing a ${LOKINET_XSAN} sanitizer build") endif() include(cmake/coverage.cmake) # these vars are set by the cmake toolchain spec if (WOW64_CROSS_COMPILE OR WIN64_CROSS_COMPILE) include(cmake/cross_compile.cmake) endif() if(NOT APPLE) if(LOKINET_NATIVE_BUILD) if(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le) add_compile_options(-mcpu=native -mtune=native) else() add_compile_options(-march=native -mtune=native) endif() elseif(NOT NON_PC_TARGET) if (LOKINET_AVX2) add_compile_options(-march=haswell -mtune=haswell -mfpmath=sse) else() # Public binary releases add_compile_options(-march=nocona -mtune=haswell -mfpmath=sse) endif() endif() endif() # now have libevent2.21 w/ pthreads set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) unset(GIT_VERSION) unset(GIT_VERSION_REAL) if(NOT GIT_VERSION) exec_program("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "rev-parse --short HEAD" OUTPUT_VARIABLE GIT_VERSION_UNSTRIP) string(STRIP "${GIT_VERSION_UNSTRIP}" GIT_VERSION) endif() string(REGEX REPLACE "^fatal.*$" nogit GIT_VERSION_REAL "${GIT_VERSION}") find_package(PkgConfig REQUIRED) if (NOT BUILD_STATIC_DEPS) pkg_check_modules(UNBOUND libunbound REQUIRED IMPORTED_TARGET) add_library(libunbound INTERFACE) target_link_libraries(libunbound INTERFACE PkgConfig::UNBOUND) pkg_check_modules(SD libsystemd IMPORTED_TARGET) # Default WITH_SYSTEMD to true if we found it option(WITH_SYSTEMD "enable systemd integration for sd_notify" ${SD_FOUND}) endif() add_subdirectory(external) # interface library for setting common includes, linkage and flags add_library(lokinet-base INTERFACE) target_include_directories(lokinet-base INTERFACE . include) target_link_libraries(lokinet-base INTERFACE oxen::quic fmt::fmt nlohmann_json::nlohmann_json) target_compile_features(lokinet-base INTERFACE cxx_std_20) add_library(lokinet-base-internal INTERFACE) target_include_directories(lokinet-base-internal INTERFACE include/lokinet) if(NOT LOKINET_FULL) target_compile_definitions(lokinet-base-internal INTERFACE LOKINET_EMBEDDED_ONLY) endif() target_link_libraries(lokinet-base-internal INTERFACE lokinet-base-internal_warnings) if (TARGET lokinet_static_deps) target_link_libraries(lokinet-base-internal INTERFACE lokinet_static_deps) endif() if(WITH_SYSTEMD AND (NOT ANDROID)) if(NOT SD_FOUND) message(FATAL_ERROR "libsystemd not found") endif() target_link_libraries(lokinet-base INTERFACE PkgConfig::SD) target_compile_definitions(lokinet-base INTERFACE WITH_SYSTEMD) endif() if(LOKINET_JEMALLOC AND NOT STATIC_LINK) pkg_check_modules(JEMALLOC jemalloc IMPORTED_TARGET) if(JEMALLOC_FOUND) target_link_libraries(lokinet-base INTERFACE PkgConfig::JEMALLOC) else() message(STATUS "jemalloc not found, not linking to jemalloc") endif() else() message(STATUS "jemalloc support disabled") endif() if(ANDROID) target_link_libraries(lokinet-base INTERFACE log) target_compile_definitions(lokinet-base INTERFACE ANDROID) set(ANDROID_PLATFORM_SRC android/ifaddrs.c) endif() if(LOKINET_HIVE) add_definitions(-DLOKINET_HIVE) endif() add_subdirectory(llarp) if(LOKINET_DAEMON) add_subdirectory(daemon) endif() if(LOKINET_HIVE) add_subdirectory(pybind) endif() if(LOKINET_TESTS OR LOKINET_HIVE) add_subdirectory(test) endif() if(ANDROID) add_subdirectory(jni) endif() add_subdirectory(docs) include(cmake/gui.cmake) if(APPLE AND LOKINET_FULL) macos_target_setup() endif() add_executable(liblokinet_jank_test EXCLUDE_FROM_ALL contrib/liblokinet_jank_test.cpp) target_link_libraries(liblokinet_jank_test PRIVATE liblokinet) # uninstall target if(NOT TARGET uninstall) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif() if(LOKINET_PACKAGE AND NOT APPLE) include(cmake/installer.cmake) endif() if(TARGET package) add_dependencies(package assemble_gui) endif() ================================================ FILE: CONTRIBUTING.md ================================================ [Español](CONTRIBUTING_es.md) # Do * Act like a responsible adult. * RUN `./contrib/format.sh` BEFORE COMMITING ALWAYS. # Do NOT * Bring off topic or non technical issues to the issue tracker. * Pester contributors. # We WILL * Merge Spelling mistake corrections (I have lots of those because spelling is HARD) * Merge code based on its correctness, Including patches via email from (pseudo/fully) anonymous contributors. # We WILL NOT * Accept patches with tabs * Merge large and pointless or pedantic changes, (i.e. code formatting shift) ## additional notes github's ui doesn't seem accept this file as a real code of conduct. ================================================ FILE: CONTRIBUTING_es.md ================================================ [Ingles](CONTRIBUTING.md) # Lo que debe * Actuar como un adulto responsable. * CORRER `make format` SIEMPRE ANTES DEL COMMIT. # Lo que NO debe * Plantear asuntos fuera de lugar o que no son tecnicos en el registro de problemas. * Ser un contribuyente molestoso. # Lo que nosotros HAREMOS * Integrar correcciones de Ortografia (Yo tengo un monton porque la ortografia es COMPLICADA) * Integrar codigo en base a que tan correcto es, Incluyendo parches recibidos por correo electronico de contribuyentes (pseudo/completamente) anonimos. # Lo que nosotros NO HAREMOS * Aceptar parches con tabulaciones * Integrar cambios grandes sin sentido o pedantes, (por ejemplo codigo con cambios de formato) ## notas adicionales github no parece aceptar este archivo como un codigo de conducta real. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: cmake/CMakeGraphVizOptions.cmake ================================================ set(GRAPHVIZ_GRAPH_NAME "graph.dot" CACHE STRING "") set(GRAPHVIZ_GENERATE_PER_TARGET FALSE CACHE BOOL "") set(GRAPHVIZ_GENERATE_DEPENDERS FALSE CACHE BOOL "") set(GRAPHVIZ_OBJECT_LIBS OFF CACHE BOOL "") ================================================ FILE: cmake/DownloadLibSodium.cmake ================================================ set(LIBSODIUM_PREFIX ${CMAKE_BINARY_DIR}/libsodium) set(LIBSODIUM_URL https://github.com/jedisct1/libsodium/releases/download/1.0.18-RELEASE/libsodium-1.0.18.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz) set(LIBSODIUM_HASH SHA512=17e8638e46d8f6f7d024fe5559eccf2b8baf23e143fadd472a7d29d228b186d86686a5e6920385fe2020729119a5f12f989c3a782afbd05a8db4819bb18666ef) if(SODIUM_TARBALL_URL) # make a build time override of the tarball url so we can fetch it if the original link goes away set(LIBSODIUM_URL ${SODIUM_TARBALL_URL}) endif() file(MAKE_DIRECTORY ${LIBSODIUM_PREFIX}/include) include(ExternalProject) include(ProcessorCount) ProcessorCount(PROCESSOR_COUNT) if(PROCESSOR_COUNT EQUAL 0) set(PROCESSOR_COUNT 1) endif() set(sodium_cc ${CMAKE_C_COMPILER}) if(CCACHE_PROGRAM) set(sodium_cc "${CCACHE_PROGRAM} ${sodium_cc}") endif() set(SODIUM_CONFIGURE ./configure --prefix=${LIBSODIUM_PREFIX} --enable-static --disable-shared --with-pic --quiet CC=${sodium_cc}) if (CMAKE_C_COMPILER_ARG1) set(SODIUM_CONFIGURE ${SODIUM_CONFIGURE} CPPFLAGS=${CMAKE_C_COMPILER_ARG1}) endif() if (CROSS_TARGET) set(SODIUM_CONFIGURE ${SODIUM_CONFIGURE} --target=${CROSS_TARGET} --host=${CROSS_TARGET}) endif() ExternalProject_Add(libsodium_external BUILD_IN_SOURCE ON PREFIX ${LIBSODIUM_PREFIX} URL ${LIBSODIUM_URL} URL_HASH ${LIBSODIUM_HASH} CONFIGURE_COMMAND ${SODIUM_CONFIGURE} PATCH_COMMAND patch -p1 -d < ${CMAKE_SOURCE_DIR}/win32-setup/libsodium-1.0.18-win32.patch BUILD_COMMAND make -j${PROCESSOR_COUNT} INSTALL_COMMAND ${MAKE} BUILD_BYPRODUCTS ${LIBSODIUM_PREFIX}/lib/libsodium.a ${LIBSODIUM_PREFIX}/include ) add_library(sodium_vendor STATIC IMPORTED GLOBAL) add_dependencies(sodium_vendor libsodium_external) set_target_properties(sodium_vendor PROPERTIES IMPORTED_LOCATION ${LIBSODIUM_PREFIX}/lib/libsodium.a INTERFACE_INCLUDE_DIRECTORIES ${LIBSODIUM_PREFIX}/include ) ================================================ FILE: cmake/FindJemalloc.cmake ================================================ # # Find the JEMALLOC client includes and library # # This module defines # JEMALLOC_INCLUDE_DIR, where to find jemalloc.h # JEMALLOC_LIBRARIES, the libraries to link against # JEMALLOC_FOUND, if false, you cannot build anything that requires JEMALLOC # also defined, but not for general use are # JEMALLOC_LIBRARY, where to find the JEMALLOC library. set( JEMALLOC_FOUND 0 ) if ( UNIX ) FIND_PATH( JEMALLOC_INCLUDE_DIR NAMES jemalloc/jemalloc.h PATHS /usr/include /usr/include/jemalloc /usr/local/include /usr/local/include/jemalloc $ENV{JEMALLOC_ROOT} $ENV{JEMALLOC_ROOT}/include DOC "Specify include-directories that might contain jemalloc.h here." ) FIND_LIBRARY( JEMALLOC_LIBRARY NAMES jemalloc libjemalloc JEMALLOC PATHS /usr/lib /usr/lib/jemalloc /usr/local/lib /usr/local/lib/jemalloc /usr/local/jemalloc/lib $ENV{JEMALLOC_ROOT}/lib $ENV{JEMALLOC_ROOT} DOC "Specify library-locations that might contain the jemalloc library here." ) if ( JEMALLOC_LIBRARY ) if ( JEMALLOC_INCLUDE_DIR ) set( JEMALLOC_FOUND 1 ) message( STATUS "Found JEMALLOC library: ${JEMALLOC_LIBRARY}") message( STATUS "Found JEMALLOC headers: ${JEMALLOC_INCLUDE_DIR}") else ( JEMALLOC_INCLUDE_DIR ) message(FATAL_ERROR "Could not find jemalloc headers! Please install jemalloc libraries and headers") endif ( JEMALLOC_INCLUDE_DIR ) endif ( JEMALLOC_LIBRARY ) add_library(jemalloc SHARED IMPORTED) set_target_properties(jemalloc PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${JEMALLOC_INCLUDE_DUR}" IMPORTED_LOCATION "${JEMALLOC_LIBRARY}") mark_as_advanced( JEMALLOC_FOUND JEMALLOC_LIBRARY JEMALLOC_EXTRA_LIBRARIES JEMALLOC_INCLUDE_DIR ) endif (UNIX) ================================================ FILE: cmake/GenVersion.cmake ================================================ # Copyright (c) 2014-2019, The Monero Project # Copyright (c) 2019, The Loki Project # # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are # permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, this list # of conditions and the following disclaimer in the documentation and/or other # materials provided with the distribution. # # 3. Neither the name of the copyright holder nor the names of its contributors may be # used to endorse or promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL # THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers # Check what commit we're on execute_process(COMMAND "${GIT}" rev-parse --short HEAD RESULT_VARIABLE RET OUTPUT_VARIABLE COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE) if(RET) # Something went wrong, set the version tag to -unknown message(WARNING "Cannot determine current commit. Make sure that you are building either from a Git working tree or from a source archive.") set(VERSIONTAG "unknown") else() message(STATUS "You are currently on commit ${COMMIT}") # Get all the tags execute_process(COMMAND "${GIT}" rev-list --tags --max-count=1 --abbrev-commit RESULT_VARIABLE RET OUTPUT_VARIABLE TAGGEDCOMMIT OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT TAGGEDCOMMIT) message(WARNING "Cannot determine most recent tag. Make sure that you are building either from a Git working tree or from a source archive.") set(VERSIONTAG "${COMMIT}") else() # Check if we're building that tagged commit or a different one if(COMMIT STREQUAL TAGGEDCOMMIT) message(STATUS "${COMMIT} is a tagged release; setting version tag to 'release'") set(VERSIONTAG "release") else() message(STATUS "You are not building a tagged release; setting version tag to '${COMMIT}'") set(VERSIONTAG "${COMMIT}") endif() endif() endif() configure_file("${SRC}" "${DEST}" @ONLY) ================================================ FILE: cmake/MacroEnsureOutOfSourceBuild.cmake ================================================ # - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() # MACRO_ENSURE_OUT_OF_SOURCE_BUILD() # Copyright (c) 2006, Alexander Neundorf, # # Redistribution and use is allowed according to the terms of the BSD license: # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) if (_insource) message(SEND_ERROR "${_errorMessage}") message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") endif (_insource) endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) ================================================ FILE: cmake/StaticBuild.cmake ================================================ # cmake bits to do a full static build, downloading and building all dependencies. # Most of these are CACHE STRINGs so that you can override them using -DWHATEVER during cmake # invocation to override. include_guard(GLOBAL) include("${CMAKE_CURRENT_LIST_DIR}/../external/oxen-libquic/cmake/StaticBuild.cmake") set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") set(EXPAT_VERSION 2.7.1 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=4c9a6c1c1769d2c4404da083dd3013dbc73883da50e2b7353db2349a420e9b6d27cac7dbcb645991d6c7cdbf79bd88486fc1ac353084ce48e61081fb56e13d46 CACHE STRING "expat source hash") set(UNBOUND_VERSION 1.23.0 CACHE STRING "unbound version") set(UNBOUND_MIRROR ${LOCAL_MIRROR} https://nlnetlabs.nl/downloads/unbound CACHE STRING "unbound download mirror(s)") set(UNBOUND_SOURCE unbound-${UNBOUND_VERSION}.tar.gz) set(UNBOUND_HASH SHA512=9b5ca48f4f5189f168f76396f5895f39262a4333e589f8c64bb9298a55c6266f626a4a4399370c68edd9f6318215a401146bf9e16a101c54decf623668a398af CACHE STRING "unbound source hash") set(SQLITE3_VERSION 3500200 CACHE STRING "sqlite3 version") set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2025 CACHE STRING "sqlite3 download mirror(s)") set(SQLITE3_SOURCE sqlite-autoconf-${SQLITE3_VERSION}.tar.gz) set(SQLITE3_HASH SHA3_256=e4d2b4332988f479ec032ccff00963a9bbd24a3a0f0222b4e249653fa680b4c0 CACHE STRING "sqlite3 source hash") set(SODIUM_VERSION 1.0.20 CACHE STRING "libsodium version") set(SODIUM_MIRROR ${LOCAL_MIRROR} https://download.libsodium.org/libsodium/releases https://github.com/jedisct1/libsodium/releases/download/${SODIUM_VERSION}-RELEASE CACHE STRING "libsodium mirror(s)") set(SODIUM_SOURCE libsodium-${SODIUM_VERSION}.tar.gz) set(SODIUM_HASH SHA512=7ea165f3c1b1609790e30a16348b9dfdc5731302da00c07c65e125c8ab115c75419a5631876973600f8a4b560ca2c8267001770b68f2eb3eebc9ba095d312702 CACHE STRING "libsodium source hash") set(ZMQ_VERSION 4.3.5 CACHE STRING "libzmq version") set(ZMQ_MIRROR ${LOCAL_MIRROR} https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION} CACHE STRING "libzmq mirror(s)") set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz) set(ZMQ_HASH SHA512=a71d48aa977ad8941c1609947d8db2679fc7a951e4cd0c3a1127ae026d883c11bd4203cf315de87f95f5031aec459a731aec34e5ce5b667b8d0559b157952541 CACHE STRING "libzmq source hash") set(ZLIB_VERSION 1.3.1 CACHE STRING "zlib version") set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net CACHE STRING "zlib mirror(s)") set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) set(ZLIB_HASH SHA256=38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32 CACHE STRING "zlib source hash") set(ZSTD_VERSION 1.5.7 CACHE STRING "zstd version") set(ZSTD_MIRROR ${LOCAL_MIRROR} https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION} CACHE STRING "zstd mirror(s)") set(ZSTD_SOURCE zstd-${ZSTD_VERSION}.tar.gz) set(ZSTD_HASH SHA256=eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 CACHE STRING "zstd source hash") include(ExternalProject) set(DEPS_DESTDIR ${CMAKE_BINARY_DIR}/static-deps) set(DEPS_SOURCEDIR ${CMAKE_BINARY_DIR}/static-deps-sources) include_directories(BEFORE SYSTEM ${DEPS_DESTDIR}/include) file(MAKE_DIRECTORY ${DEPS_DESTDIR}/include) set(deps_cc "${CMAKE_C_COMPILER}") set(deps_cxx "${CMAKE_CXX_COMPILER}") if(CMAKE_C_COMPILER_LAUNCHER) set(deps_cc "${CMAKE_C_COMPILER_LAUNCHER} ${deps_cc}") endif() if(CMAKE_CXX_COMPILER_LAUNCHER) set(deps_cxx "${CMAKE_CXX_COMPILER_LAUNCHER} ${deps_cxx}") endif() function(expand_urls output source_file) set(expanded) foreach(mirror ${ARGN}) list(APPEND expanded "${mirror}/${source_file}") endforeach() set(${output} "${expanded}" PARENT_SCOPE) endfunction() add_library(lokinet_static_deps INTERFACE) function(add_static_target target ext_target libname) add_library(${target} STATIC IMPORTED GLOBAL) add_dependencies(${target} ${ext_target}) target_link_libraries(lokinet_static_deps INTERFACE ${target}) set_target_properties(${target} PROPERTIES IMPORTED_LOCATION ${DEPS_DESTDIR}/lib/${libname} ) if (ARGN) target_link_libraries(${target} INTERFACE ${ARGN}) endif() endfunction() set(cross_host "") set(cross_rc "") if(CMAKE_CROSSCOMPILING) set(cross_host "--host=${ARCH_TRIPLET}") if (ARCH_TRIPLET MATCHES mingw AND CMAKE_RC_COMPILER) set(cross_rc "WINDRES=${CMAKE_RC_COMPILER}") endif() endif() if(ANDROID) set(android_toolchain_suffix linux-android) set(android_compiler_suffix linux-android23) if(CMAKE_ANDROID_ARCH_ABI MATCHES x86_64) set(android_machine x86_64) set(cross_host "--host=x86_64-linux-android") set(android_compiler_prefix x86_64) set(android_compiler_suffix linux-android23) set(android_toolchain_prefix x86_64) set(android_toolchain_suffix linux-android) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86) set(android_machine x86) set(cross_host "--host=i686-linux-android") set(android_compiler_prefix i686) set(android_compiler_suffix linux-android23) set(android_toolchain_prefix i686) set(android_toolchain_suffix linux-android) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a) set(android_machine arm) set(cross_host "--host=armv7a-linux-androideabi") set(android_compiler_prefix armv7a) set(android_compiler_suffix linux-androideabi23) set(android_toolchain_prefix arm) set(android_toolchain_suffix linux-androideabi) elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a) set(android_machine arm64) set(cross_host "--host=aarch64-linux-android") set(android_compiler_prefix aarch64) set(android_compiler_suffix linux-android23) set(android_toolchain_prefix aarch64) set(android_toolchain_suffix linux-android) else() message(FATAL_ERROR "unknown android arch: ${CMAKE_ANDROID_ARCH_ABI}") endif() set(deps_cc "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/${android_compiler_prefix}-${android_compiler_suffix}-clang") set(deps_cxx "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/${android_compiler_prefix}-${android_compiler_suffix}-clang++") set(deps_ld "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/${android_compiler_prefix}-${android_toolchain_suffix}-ld") set(deps_ranlib "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/${android_toolchain_prefix}-${android_toolchain_suffix}-ranlib") set(deps_ar "${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/${android_toolchain_prefix}-${android_toolchain_suffix}-ar") endif() set(deps_CFLAGS "-O2") set(deps_CXXFLAGS "-O2") if(WITH_LTO) set(deps_CFLAGS "${deps_CFLAGS} -flto") endif() if(APPLE) set(deps_CFLAGS "${deps_CFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") set(deps_CXXFLAGS "${deps_CXXFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() if(_winver) set(deps_CFLAGS "${deps_CFLAGS} -D_WIN32_WINNT=${_winver}") set(deps_CXXFLAGS "${deps_CXXFLAGS} -D_WIN32_WINNT=${_winver}") endif() if("${CMAKE_GENERATOR}" STREQUAL "Unix Makefiles") set(_make $(MAKE)) else() set(_make make) endif() # Builds a target; takes the target name (e.g. "readline") and builds it in an external project with # target name suffixed with `_external`. Its upper-case value is used to get the download details # (from the variables set above). The following options are supported and passed through to # ExternalProject_Add if specified. If omitted, these defaults are used: set(build_def_DEPENDS "") set(build_def_PATCH_COMMAND "") set(build_def_CONFIGURE_COMMAND ./configure ${cross_host} --disable-shared --prefix=${DEPS_DESTDIR} --with-pic "CC=${deps_cc}" "CXX=${deps_cxx}" "CFLAGS=${deps_CFLAGS}" "CXXFLAGS=${deps_CXXFLAGS}" ${cross_rc}) set(build_def_BUILD_COMMAND ${_make}) set(build_def_INSTALL_COMMAND ${_make} install) set(build_def_BUILD_BYPRODUCTS ${DEPS_DESTDIR}/lib/lib___TARGET___.a ${DEPS_DESTDIR}/include/___TARGET___.h) function(build_external target) set(options DEPENDS PATCH_COMMAND CONFIGURE_COMMAND BUILD_COMMAND INSTALL_COMMAND BUILD_BYPRODUCTS) cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "${options}") foreach(o ${options}) if(NOT DEFINED arg_${o}) set(arg_${o} ${build_def_${o}}) endif() endforeach() string(REPLACE ___TARGET___ ${target} arg_BUILD_BYPRODUCTS "${arg_BUILD_BYPRODUCTS}") if(arg_CONFIGURE_COMMAND MATCHES "^DEFAULT_CMAKE") string(REGEX REPLACE "^DEFAULT_CMAKE(;?)" "CMAKE_ARGS;-DCMAKE_INSTALL_PREFIX=${DEPS_DESTDIR}\\1" configure "${arg_CONFIGURE_COMMAND}") else() set(configure CONFIGURE_COMMAND ${arg_CONFIGURE_COMMAND}) endif() string(TOUPPER "${target}" prefix) expand_urls(urls ${${prefix}_SOURCE} ${${prefix}_MIRROR}) ExternalProject_Add("${target}_external" DEPENDS ${arg_DEPENDS} BUILD_IN_SOURCE ON PREFIX ${DEPS_SOURCEDIR} URL ${urls} URL_HASH ${${prefix}_HASH} DOWNLOAD_NO_PROGRESS ON PATCH_COMMAND ${arg_PATCH_COMMAND} ${configure} BUILD_COMMAND ${arg_BUILD_COMMAND} INSTALL_COMMAND ${arg_INSTALL_COMMAND} BUILD_BYPRODUCTS ${arg_BUILD_BYPRODUCTS} ) endfunction() if(NOT TARGET sodium) build_external(sodium CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared --enable-static --with-pic "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}") add_static_target(sodium sodium_external libsodium.a) endif() if(LOKINET_PEERSTATS) build_external(sqlite3) add_static_target(sqlite3 sqlite3_external libsqlite3.a) endif() if(NOT TARGET libzstd::static) build_external(zstd CONFIGURE_COMMAND DEFAULT_CMAKE -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_BUILD_TESTS=OFF -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_SHARED=OFF -DZSTD_BUILD_DICTBUILDER=OFF SOURCE_SUBDIR build/cmake BUILD_BYPRODUCTS ${DEPS_DESTDIR}/lib/libzstd.a ${DEPS_DESTDIR}/include/zstd.h ) # Use the same libzstd::static target name as libsession-util so that we can use libsession's # static zstd if we are being built as part of libsession: add_static_target(libzstd::static zstd_external libzstd.a) endif() if(LOKINET_FULL) build_external(zlib CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS} -fPIC" ${cross_extra} ./configure --prefix=${DEPS_DESTDIR} --static BUILD_BYPRODUCTS ${DEPS_DESTDIR}/lib/libz.a ${DEPS_DESTDIR}/include/zlib.h ) add_static_target(zlib zlib_external libz.a) build_external(expat CONFIGURE_COMMAND ./configure ${cross_host} --prefix=${DEPS_DESTDIR} --enable-static --disable-shared --with-pic --without-examples --without-tests --without-docbook --without-xmlwf "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}" ) add_static_target(expat expat_external libexpat.a) if(WIN32) set(unbound_patch PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh ${PROJECT_SOURCE_DIR}/contrib/patches/unbound-delete-crash-fix.patch) endif() build_external(unbound DEPENDS nettle_external expat_external ${unbound_patch} CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --with-libunbound-only --disable-shared --enable-static --with-pic --$,enable,disable>-flto --with-nettle=${DEPS_DESTDIR} --with-libexpat=${DEPS_DESTDIR} --without-ssl "CC=${deps_cc}" "CFLAGS=${deps_CFLAGS}" "LDFLAGS=${unbound_ldflags}" ) add_static_target(libunbound unbound_external libunbound.a) if(NOT WIN32) set_target_properties(libunbound PROPERTIES INTERFACE_LINK_LIBRARIES "hogweed::hogweed;nettle::nettle") else() set_target_properties(libunbound PROPERTIES INTERFACE_LINK_LIBRARIES "hogweed::hogweed;nettle::nettle;ws2_32;crypt32;iphlpapi") endif() if(ARCH_TRIPLET MATCHES mingw) option(WITH_WEPOLL "use wepoll zmq poller (crashy)" OFF) if(WITH_WEPOLL) set(zmq_extra --with-poller=wepoll) endif() endif() build_external(zmq DEPENDS sodium_external CONFIGURE_COMMAND ./configure ${cross_host} --prefix=${DEPS_DESTDIR} --enable-static --disable-shared --disable-curve-keygen --enable-curve --disable-drafts --disable-libunwind --with-libsodium --without-pgm --without-norm --without-vmci --without-docs --with-pic --disable-Werror --disable-libbsd ${zmq_extra} "CC=${deps_cc}" "CXX=${deps_cxx}" "CFLAGS=${deps_CFLAGS} -fstack-protector" "CXXFLAGS=${deps_CXXFLAGS} -fstack-protector" "sodium_CFLAGS=-I${DEPS_DESTDIR}/include" "sodium_LIBS=-L${DEPS_DESTDIR}/lib -lsodium" ) add_static_target(libzmq zmq_external libzmq.a) set(libzmq_link_libs "sodium") if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) list(APPEND libzmq_link_libs iphlpapi) endif() set_target_properties(libzmq PROPERTIES INTERFACE_LINK_LIBRARIES "${libzmq_link_libs}" INTERFACE_COMPILE_DEFINITIONS "ZMQ_STATIC") endif(LOKINET_FULL) ================================================ FILE: cmake/TargetArch.cmake ================================================ # Based on the Qt 5 processor detection code, so should be very accurate # https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h # Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64) # Regarding POWER/PowerPC, just as is noted in the Qt source, # "There are many more known variants/revisions that we do not handle/detect." set(archdetect_c_code " #if defined(__arm__) || defined(__TARGET_ARCH_ARM) #if defined(__ARM_ARCH_7__) \\ || defined(__ARM_ARCH_7A__) \\ || defined(__ARM_ARCH_7R__) \\ || defined(__ARM_ARCH_7M__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7) #error cmake_ARCH armv7 #elif defined(__ARM_ARCH_6__) \\ || defined(__ARM_ARCH_6J__) \\ || defined(__ARM_ARCH_6T2__) \\ || defined(__ARM_ARCH_6Z__) \\ || defined(__ARM_ARCH_6K__) \\ || defined(__ARM_ARCH_6ZK__) \\ || defined(__ARM_ARCH_6M__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6) #error cmake_ARCH armv6 #elif defined(__ARM_ARCH_5TEJ__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5) #error cmake_ARCH armv5 #else #error cmake_ARCH arm #endif #elif defined(__i386) || defined(__i386__) || defined(_M_IX86) #error cmake_ARCH i386 #elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) #error cmake_ARCH x86_64 #elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) #error cmake_ARCH ia64 #elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) #error cmake_ARCH ppc64 #else #error cmake_ARCH ppc #endif #endif #error cmake_ARCH unknown ") # Set ppc_support to TRUE before including this file or ppc and ppc64 # will be treated as invalid architectures since they are no longer supported by Apple function(target_architecture output_var) if(APPLE AND CMAKE_OSX_ARCHITECTURES) # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set # First let's normalize the order of the values # Note that it's not possible to compile PowerPC applications if you are using # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we # disable it by default # See this page for more information: # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if("${osx_arch}" STREQUAL "ppc" AND ppc_support) set(osx_arch_ppc TRUE) elseif("${osx_arch}" STREQUAL "i386") set(osx_arch_i386 TRUE) elseif("${osx_arch}" STREQUAL "x86_64") set(osx_arch_x86_64 TRUE) elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support) set(osx_arch_ppc64 TRUE) else() message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}") endif() endforeach() # Now add all the architectures in our normalized order if(osx_arch_ppc) list(APPEND ARCH ppc) endif() if(osx_arch_i386) list(APPEND ARCH i386) endif() if(osx_arch_x86_64) list(APPEND ARCH x86_64) endif() if(osx_arch_ppc64) list(APPEND ARCH ppc64) endif() else() file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") enable_language(C) # Detect the architecture in a rather creative way... # This compiles a small C program which is a series of ifdefs that selects a # particular #error preprocessor directive whose message string contains the # target architecture. The program will always fail to compile (both because # file is not a valid C program, and obviously because of the presence of the # #error preprocessor directives... but by exploiting the preprocessor in this # way, we can detect the correct target architecture even when cross-compiling, # since the program itself never needs to be run (only the compiler/preprocessor) try_run( run_result_unused compile_result_unused "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/arch.c" COMPILE_OUTPUT_VARIABLE ARCH CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} ) # Parse the architecture name from the compiler output string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") # Get rid of the value marker leaving just the architecture name string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") # If we are compiling with an unknown architecture this variable should # already be set to "unknown" but in the case that it's empty (i.e. due # to a typo in the code), then set it to unknown if (NOT ARCH) set(ARCH unknown) endif() endif() set(${output_var} "${ARCH}" PARENT_SCOPE) endfunction() ================================================ FILE: cmake/Version.cmake ================================================ # We do this via a custom command that re-invokes a cmake script because we need the DEPENDS on .git/index so that we will re-run it (to regenerate the commit tag in the version) whenever the current commit changes. If we used a configure_file directly here, it would only re-run when something else causes cmake to re-run. if(LOKINET_VERSIONTAG) set(VERSIONTAG "${LOKINET_VERSIONTAG}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" @ONLY) else() set(VERSIONTAG "${GIT_VERSION}") set(GIT_INDEX_FILE "${PROJECT_SOURCE_DIR}/.git/index") find_package(Git) if(EXISTS "${GIT_INDEX_FILE}" AND ( GIT_FOUND OR Git_FOUND) ) message(STATUS "Found Git: ${GIT_EXECUTABLE}") set(genversion_args "-DGIT=${GIT_EXECUTABLE}") foreach(v lokinet_VERSION lokinet_VERSION_MAJOR lokinet_VERSION_MINOR lokinet_VERSION_PATCH RELEASE_MOTTO) list(APPEND genversion_args "-D${v}=${${v}}") endforeach() add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" COMMAND "${CMAKE_COMMAND}" ${genversion_args} "-D" "SRC=${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "-D" "DEST=${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" "-P" "${CMAKE_CURRENT_LIST_DIR}/GenVersion.cmake" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${GIT_INDEX_FILE}") else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" @ONLY) endif() endif() if(WIN32) foreach(exe IN ITEMS lokinet lokinet-vpn lokinet-bootstrap) set(lokinet_EXE_NAME "${exe}.exe") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/win32/version.rc.in" "${CMAKE_BINARY_DIR}/${exe}.rc" @ONLY) set_property(SOURCE "${CMAKE_BINARY_DIR}/${exe}.rc" PROPERTY GENERATED 1) endforeach() endif() add_custom_target(genversion_cpp DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp") if(WIN32) add_custom_target(genversion_rc DEPENDS "${CMAKE_BINARY_DIR}/lokinet.rc" "${CMAKE_BINARY_DIR}/lokinet-vpn.rc" "${CMAKE_BINARY_DIR}/lokinet-bootstrap.rc") else() add_custom_target(genversion_rc) endif() add_custom_target(genversion DEPENDS genversion_cpp genversion_rc) ================================================ FILE: cmake/add_import_library.cmake ================================================ function(add_import_library libname) add_library(libname SHARED IMPORTED) if(NOT TARGET libname) message(FATAL "unable to find library ${libname}") endif() endfunction() ================================================ FILE: cmake/cmake_uninstall.cmake.in ================================================ if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") endif() file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif() else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif() endforeach() ================================================ FILE: cmake/coverage.cmake ================================================ if (LOKINET_COVERAGE) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options( -fprofile-instr-generate -fcoverage-mapping ) link_libraries( -fprofile-instr-generate ) else() add_compile_options( --coverage -g0 ) link_libraries( --coverage ) endif() endif() ================================================ FILE: cmake/cross_compile.cmake ================================================ # dynamic linking does this all the time if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") option(NO_LIBGCC "use libunwind+compiler-rt instead, must already be installed in mingw-w64 sysroot" OFF) add_compile_options(-Wno-unused-command-line-argument -Wno-c++11-narrowing) add_compile_options($<$:-Wno-bad-function-cast>) if (NO_LIBGCC) find_library(UNWIND_LIB unwind) link_libraries(${UNWIND_LIB}) find_library(PSAPI_LIB psapi) link_libraries(${PSAPI_LIB}) endif(NO_LIBGCC) else() # found it. this is GNU only add_compile_options(-Wno-cast-function-type) endif() ================================================ FILE: cmake/enable_lto.cmake ================================================ # -flto include(CheckIPOSupported) option(WITH_LTO "enable lto on compile time" ON) if(WITH_LTO) if(WIN32) message(FATAL_ERROR "LTO not supported on win32 targets, please set -DWITH_LTO=OFF") endif() check_ipo_supported(RESULT IPO_ENABLED OUTPUT ipo_error) if(IPO_ENABLED) message(STATUS "LTO enabled") else() message(WARNING "LTO not supported by compiler: ${ipo_error}") endif() else() message(STATUS "LTO disabled") set(IPO_ENABLED OFF) endif() function(enable_lto) if(IPO_ENABLED) set_target_properties(${ARGN} PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON) endif() endfunction() ================================================ FILE: cmake/gui-option.cmake ================================================ set(default_build_gui OFF) if(LOKINET_DAEMON AND (APPLE OR WIN32)) set(default_build_gui ON) endif() if(WIN32) set(GUI_EXE "" CACHE FILEPATH "path to a pre-built Windows GUI .exe to use (implies -DLOKINET_GUI=OFF)") if(GUI_EXE) set(default_build_gui OFF) endif() endif() option(LOKINET_GUI "build electron gui from 'gui' submodule source" ${default_build_gui}) if(LOKINET_GUI AND GUI_EXE) message(FATAL_ERROR "-DGUI_EXE=... and -DLOKINET_GUI=ON are mutually exclusive") endif() ================================================ FILE: cmake/gui.cmake ================================================ if(WIN32 AND GUI_EXE) message(STATUS "using pre-built lokinet gui executable: ${GUI_EXE}") execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GUI_EXE}" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") elseif(LOKINET_GUI) message(STATUS "Building lokinet-gui from source") set(default_gui_target pack) if(APPLE) set(default_gui_target macos:raw) elseif(WIN32) set(default_gui_target win32) endif() set(GUI_YARN_TARGET "${default_gui_target}" CACHE STRING "yarn target for building the GUI") set(GUI_YARN_EXTRA_OPTS "" CACHE STRING "extra options to pass into the yarn build command") # allow manually specifying yarn with -DYARN= if(NOT YARN) find_program(YARN NAMES yarnpkg yarn REQUIRED) endif() message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") if(NOT WIN32) add_custom_target(lokinet-gui COMMAND ${YARN} install --frozen-lockfile && ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") endif() if(APPLE) add_custom_target(assemble_gui ALL DEPENDS assemble lokinet-gui COMMAND mkdir "${lokinet_app}/Contents/Helpers" COMMAND cp -a "${PROJECT_SOURCE_DIR}/gui/release/mac/Lokinet-GUI.app" "${lokinet_app}/Contents/Helpers/" COMMAND mkdir -p "${lokinet_app}/Contents/Resources/en.lproj" COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${lokinet_app}/Contents/Resources/en.lproj/" COMMAND cp "${lokinet_app}/Contents/Resources/icon.icns" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/icon.icns" COMMAND cp "${PROJECT_SOURCE_DIR}/contrib/macos/InfoPlist.strings" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Resources/en.lproj/" COMMAND /usr/libexec/PlistBuddy -c "Delete :CFBundleDisplayName" -c "Add :LSHasLocalizedDisplayName bool true" -c "Add :CFBundleDevelopmentRegion string en" -c "Set :CFBundleShortVersionString ${lokinet_VERSION}" -c "Set :CFBundleVersion ${lokinet_VERSION}.${LOKINET_APPLE_BUILD}" "${lokinet_app}/Contents/Helpers/Lokinet-GUI.app/Contents/Info.plist" ) elseif(WIN32) file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui") add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" COMMAND ${YARN} install --frozen-lockfile && USE_SYSTEM_7ZA=true DISPLAY= WINEDEBUG=-all WINEPREFIX="${PROJECT_BINARY_DIR}/wineprefix" ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PROJECT_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") add_custom_target(assemble_gui ALL COMMAND "true" DEPENDS "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") else() message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform") endif() else() message(STATUS "not building gui") endif() if(NOT TARGET assemble_gui) add_custom_target(assemble_gui COMMAND "true") endif() ================================================ FILE: cmake/installer.cmake ================================================ set(CPACK_PACKAGE_VENDOR "lokinet.org") set(CPACK_PACKAGE_HOMEPAGE_URL "https://lokinet.org/") set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/contrib/readme-installer.txt") set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") if(WIN32) include(cmake/win32_installer_deps.cmake) install(FILES ${CMAKE_SOURCE_DIR}/contrib/configs/00-exit.ini DESTINATION share/conf.d COMPONENT exit_configs) install(FILES ${CMAKE_SOURCE_DIR}/contrib/configs/00-keyfile.ini DESTINATION share/conf.d COMPONENT keyfile_configs) install(FILES ${CMAKE_SOURCE_DIR}/contrib/configs/00-debug-log.ini DESTINATION share/conf.d COMPONENT debug_configs) get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified" "lokinet" "gui" "exit_configs" "keyfile_configs" "debug_configs") list(APPEND CPACK_COMPONENTS_ALL "lokinet" "gui" "exit_configs" "keyfile_configs" "debug_configs") elseif(APPLE) set(CPACK_GENERATOR DragNDrop;ZIP) get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") endif() include(CPack) if(WIN32) cpack_add_component(lokinet DISPLAY_NAME "lokinet" DESCRIPTION "core required lokinet files" REQUIRED) cpack_add_component(gui DISPLAY_NAME "lokinet gui" DESCRIPTION "electron based control panel for lokinet") cpack_add_component(exit_configs DISPLAY_NAME "auto-enable exit" DESCRIPTION "automatically enable usage of exit.loki as an exit node\n" DISABLED) cpack_add_component(keyfile_configs DISPLAY_NAME "persist address" DESCRIPTION "persist .loki address across restarts of lokinet\nnot recommended when enabling exit nodes" DISABLED) cpack_add_component(debug_configs DISPLAY_NAME "debug logging" DESCRIPTION "enable debug spew log level by default" DISABLED) endif() ================================================ FILE: cmake/libatomic.cmake ================================================ function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) if (EMBEDDED_CFG) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -m32 -march=i486") elseif(MSVC OR MSVC_VERSION) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -arch:IA32 -std:c++14") else() # CMAKE_CXX_STANDARD does not propagate to cmake compile tests set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++14") endif() check_cxx_source_compiles(" #include #include std::atomic x (0); int main() { uint64_t i = x.load(std::memory_order_relaxed); return 0; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction() function(link_libatomic) check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) if(HAVE_CXX_ATOMICS64_WITHOUT_LIB) message(STATUS "Have working 64bit atomics") return() endif() if (NOT MSVC AND NOT MSVC_VERSION) check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) if (HAVE_CXX_LIBATOMICS64) message(STATUS "Have 64bit atomics via library") list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) if (HAVE_CXX_ATOMICS64_WITH_LIB) message(STATUS "Can link with libatomic") link_libraries(-latomic) return() endif() endif() endif() if (MSVC OR MSVC_VERSION) message(FATAL_ERROR "Host compiler must support 64-bit std::atomic! (What does MSVC do to inline atomics?)") else() message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!") endif() endfunction() ================================================ FILE: cmake/macos.cmake ================================================ if((NOT APPLE) OR NOT LOKINET_DAEMON) return() endif() option(MACOS_SYSTEM_EXTENSION "Build the network extension as a system extension rather than a plugin. This must be ON for non-app store release builds, and must be OFF for dev builds and Mac App Store distribution builds" OFF) option(CODESIGN "codesign the resulting app and extension" ON) set(CODESIGN_ID "" CACHE STRING "codesign the macos app using this key identity; if empty we'll try to guess") set(default_profile_type "dev") if(MACOS_SYSTEM_EXTENSION) set(default_profile_type "release") endif() set(CODESIGN_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.${default_profile_type}.provisionprofile" CACHE FILEPATH "Path to a .provisionprofile to use for the main app") set(CODESIGN_EXT_PROFILE "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.${default_profile_type}.provisionprofile" CACHE FILEPATH "Path to a .provisionprofile to use for the lokinet extension") if(CODESIGN AND NOT CODESIGN_ID) if(MACOS_SYSTEM_EXTENSION) set(codesign_cert_pattern "Developer ID Application") else() set(codesign_cert_pattern "Apple Development") endif() execute_process( COMMAND security find-identity -v -p codesigning COMMAND sed -n "s/^ *[0-9][0-9]*) *\\([A-F0-9]\\{40\\}\\) *\"\\(${codesign_cert_pattern}.*\\)\"\$/\\1 \\2/p" RESULT_VARIABLE find_id_exit_code OUTPUT_VARIABLE find_id_output) if(NOT find_id_exit_code EQUAL 0) message(FATAL_ERROR "Finding signing identities with security find-identity failed; try specifying an id using -DCODESIGN_ID=...") endif() string(REGEX MATCHALL "(^|\n)[0-9A-F]+" find_id_sign_id "${find_id_output}") if(NOT find_id_sign_id) message(FATAL_ERROR "Did not find any \"${codesign_cert_pattern}\" identity; try specifying an id using -DCODESIGN_ID=...") endif() if (find_id_sign_id MATCHES ";") message(FATAL_ERROR "Found multiple \"${codesign_cert_pattern}\" identities:\n${find_id_output}\nSpecify an identify using -DCODESIGN_ID=...") endif() set(CODESIGN_ID "${find_id_sign_id}" CACHE STRING "" FORCE) endif() if(CODESIGN) message(STATUS "Codesigning using ${CODESIGN_ID}") if (NOT MACOS_NOTARIZE_USER AND NOT MACOS_NOTARIZE_PASS AND NOT MACOS_NOTARIZE_ASC AND EXISTS "$ENV{HOME}/.notarization.cmake") message(STATUS "Loading notarization info from ~/.notarization.cmake") include("$ENV{HOME}/.notarization.cmake") endif() if (MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) message(STATUS "Enabling notarization with account ${MACOS_NOTARIZE_ASC}/${MACOS_NOTARIZE_USER}") else() message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization will fail; see contrib/macos/README.txt") endif() else() message(WARNING "Codesigning disabled; the resulting build will not run on most macOS systems") endif() foreach(prof IN ITEMS CODESIGN_PROFILE CODESIGN_EXT_PROFILE) if(NOT ${prof}) message(WARNING "Missing a ${prof} provisioning profile: Apple will most likely log an uninformative error message to the system log and then kill harmless kittens if you try to run the result") elseif(NOT EXISTS "${${prof}}") message(FATAL_ERROR "Provisioning profile ${${prof}} does not exist; fix your -D${prof} path") endif() endforeach() message(STATUS "Using ${CODESIGN_PROFILE} app provisioning profile") message(STATUS "Using ${CODESIGN_EXT_PROFILE} extension provisioning profile") set(lokinet_installer "${PROJECT_BINARY_DIR}/Lokinet ${PROJECT_VERSION}") if(NOT CODESIGN) set(lokinet_installer "${lokinet_installer}-UNSIGNED") endif() set(lokinet_app "${lokinet_installer}/Lokinet.app") if(MACOS_SYSTEM_EXTENSION) set(lokinet_ext_dir Contents/Library/SystemExtensions) else() set(lokinet_ext_dir Contents/PlugIns) endif() if(CODESIGN) if(MACOS_SYSTEM_EXTENSION) set(LOKINET_ENTITLEMENTS_TYPE sysext) set(notarize_py_is_sysext True) else() set(LOKINET_ENTITLEMENTS_TYPE plugin) set(notarize_py_is_sysext False) endif() configure_file( "${PROJECT_SOURCE_DIR}/contrib/macos/sign.sh.in" "${PROJECT_BINARY_DIR}/sign.sh" @ONLY) add_custom_target( sign DEPENDS "${PROJECT_BINARY_DIR}/sign.sh" COMMAND "${PROJECT_BINARY_DIR}/sign.sh" ) if(MACOS_NOTARIZE_USER AND MACOS_NOTARIZE_PASS AND MACOS_NOTARIZE_ASC) configure_file( "${PROJECT_SOURCE_DIR}/contrib/macos/notarize.py.in" "${PROJECT_BINARY_DIR}/notarize.py" @ONLY) add_custom_target( notarize DEPENDS "${PROJECT_BINARY_DIR}/notarize.py" sign COMMAND "${PROJECT_BINARY_DIR}/notarize.py" ) else() message(WARNING "You have not set one or more of MACOS_NOTARIZE_USER, MACOS_NOTARIZE_PASS, MACOS_NOTARIZE_ASC: notarization disabled") endif() else() add_custom_target(sign COMMAND "true") add_custom_target(notarize DEPENDS sign COMMAND "true") endif() set(mac_icon "${PROJECT_BINARY_DIR}/lokinet.icns") add_custom_command(OUTPUT "${mac_icon}" COMMAND ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet-mac.svg "${mac_icon}" DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet-mac.svg ${PROJECT_SOURCE_DIR}/contrib/macos/mk-icns.sh) add_custom_target(icon DEPENDS "${mac_icon}") if(LOKINET_PACKAGE) add_executable(seticon "${PROJECT_SOURCE_DIR}/contrib/macos/seticon.swift") add_custom_command(OUTPUT "${lokinet_installer}.dmg" DEPENDS notarize seticon COMMAND create-dmg --volname "Lokinet ${PROJECT_VERSION}" --volicon lokinet.icns --background "${PROJECT_SOURCE_DIR}/contrib/macos/installer.tiff" --text-size 16 --icon-size 128 --window-size 555 440 --icon Lokinet.app 151 196 --hide-extension Lokinet.app --app-drop-link 403 196 --eula "${PROJECT_SOURCE_DIR}/LICENSE" --no-internet-enable "${lokinet_installer}.dmg" "${lokinet_installer}" COMMAND ./seticon lokinet.icns "${lokinet_installer}.dmg" ) add_custom_target(dmg DEPENDS "${lokinet_installer}.dmg") endif() # Called later to set things up, after the main lokinet targets are set up function(macos_target_setup) if(NOT LOKINET_DAEMON) return() endif() if(MACOS_SYSTEM_EXTENSION) target_compile_definitions(lokinet PRIVATE MACOS_SYSTEM_EXTENSION) endif() set_target_properties(lokinet PROPERTIES OUTPUT_NAME Lokinet MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_STRING "Lokinet IP Packet Onion Router" MACOSX_BUNDLE_BUNDLE_NAME "Lokinet" MACOSX_BUNDLE_BUNDLE_VERSION "${lokinet_VERSION}" MACOSX_BUNDLE_LONG_VERSION_STRING "${lokinet_VERSION}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${lokinet_VERSION_MAJOR}.${lokinet_VERSION_MINOR}" MACOSX_BUNDLE_GUI_IDENTIFIER "org.lokinet" MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/contrib/macos/lokinet.Info.plist.in" MACOSX_BUNDLE_COPYRIGHT "© 2022, The Oxen Project" ) add_custom_target(copy_bootstrap DEPENDS lokinet-extension COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed $/Contents/Resources/bootstrap.signed ) add_dependencies(lokinet lokinet-extension icon) if(CODESIGN_PROFILE) add_custom_target(copy_prov_prof DEPENDS lokinet COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_PROFILE} $/Contents/embedded.provisionprofile ) else() add_custom_target(copy_prov_prof COMMAND true) endif() add_custom_target(assemble ALL DEPENDS lokinet lokinet-extension icon copy_prov_prof copy_bootstrap COMMAND rm -rf "${lokinet_app}" COMMAND mkdir -p "${lokinet_installer}" COMMAND cp -a $ "${lokinet_app}" COMMAND mkdir -p "${lokinet_app}/${lokinet_ext_dir}" COMMAND cp -a $ "${lokinet_app}/${lokinet_ext_dir}/" COMMAND mkdir -p "${lokinet_app}/Contents/Resources" COMMAND cp -a "${mac_icon}" "${lokinet_app}/Contents/Resources/icon.icns" ) if(LOKINET_GUI) add_dependencies(sign assemble_gui) else() add_dependencies(sign assemble) endif() endfunction() ================================================ FILE: cmake/ngtcp2_lib.cmake ================================================ # ngtcp2's top-level CMakeLists.txt loads a bunch of crap we don't want (examples, a conflicting # 'check' target, etc.); instead we directly include it's lib subdirectory to build just the # library, but we have to set up a couple things to make that work: function(add_ngtcp2_lib) file(STRINGS ngtcp2/CMakeLists.txt ngtcp2_project_line REGEX "^project\\(ngtcp2 ") if(NOT ngtcp2_project_line MATCHES "^project\\(ngtcp2 VERSION ([0-9]+)\\.([0-9]+)\\.([0-9]+)\\)$") message(FATAL_ERROR "Unable to extract ngtcp2 version from ngtcp2/CMakeLists.txt (found '${ngtcp2_project_line}')") endif() set(PACKAGE_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") include(ngtcp2/cmake/Version.cmake) HexVersion(PACKAGE_VERSION_NUM ${CMAKE_MATCH_1} ${CMAKE_MATCH_2} ${CMAKE_MATCH_3}) configure_file("ngtcp2/lib/includes/ngtcp2/version.h.in" "ngtcp2/lib/includes/ngtcp2/version.h" @ONLY) set(BUILD_SHARED_LIBS OFF) # Checks for header files. include(CheckIncludeFile) check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) check_include_file("netinet/in.h" HAVE_NETINET_IN_H) check_include_file("stddef.h" HAVE_STDDEF_H) check_include_file("stdint.h" HAVE_STDINT_H) check_include_file("stdlib.h" HAVE_STDLIB_H) check_include_file("string.h" HAVE_STRING_H) check_include_file("unistd.h" HAVE_UNISTD_H) check_include_file("sys/endian.h" HAVE_SYS_ENDIAN_H) check_include_file("endian.h" HAVE_ENDIAN_H) check_include_file("byteswap.h" HAVE_BYTESWAP_H) include(CheckTypeSize) check_type_size("ssize_t" SIZEOF_SSIZE_T) if(SIZEOF_SSIZE_T STREQUAL "") set(ssize_t ptrdiff_t) endif() include(CheckSymbolExists) if(HAVE_ENDIAN_H) check_symbol_exists(be64toh "endian.h" HAVE_BE64TOH) endif() if(NOT HAVE_BE64TO AND HAVE_SYS_ENDIAN_H) check_symbol_exists(be64toh "sys/endian.h" HAVE_BE64TOH) endif() check_symbol_exists(bswap_64 "byteswap.h" HAVE_BSWAP_64) configure_file(ngtcp2/cmakeconfig.h.in ngtcp2/config.h) include_directories("${CMAKE_CURRENT_BINARY_DIR}/ngtcp2") # for config.h set(ENABLE_STATIC_LIB ON FORCE BOOL) set(ENABLE_SHARED_LIB OFF FORCE BOOL) add_subdirectory(ngtcp2/lib EXCLUDE_FROM_ALL) target_compile_definitions(ngtcp2_static PRIVATE -DHAVE_CONFIG_H -D_GNU_SOURCE) endfunction() ================================================ FILE: cmake/solaris.cmake ================================================ if(${CMAKE_SYSTEM_NAME} MATCHES "SunOS") set(SOLARIS ON) set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lsocket -lnsl") add_definitions(-D_POSIX_PTHREAD_SEMANTICS) endif() ================================================ FILE: cmake/target_link_libraries_system.cmake ================================================ # This adds a dependency as a "system" dep - e.g -isystem function(target_link_libraries_system target) set(libs ${ARGN}) foreach(lib ${libs}) get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(${target} SYSTEM PUBLIC ${lib_include_dirs}) target_link_libraries(${target} PUBLIC ${lib}) endforeach(lib) endfunction() ================================================ FILE: cmake/unix.cmake ================================================ if(NOT ANDROID) if(NOT UNIX) return() endif() endif() include(CheckCXXSourceCompiles) include(CheckLibraryExists) add_definitions(-DUNIX) add_definitions(-DPOSIX) if(EMBEDDED_CFG OR ${CMAKE_SYSTEM_NAME} MATCHES "Linux") link_libatomic() endif() if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") add_definitions(-D_BSD_SOURCE) add_definitions(-D_GNU_SOURCE) add_definitions(-D_XOPEN_SOURCE=700) endif() ================================================ FILE: cmake/win32.cmake ================================================ if(NOT WIN32) return() endif() if (NOT STATIC_LINK) message(FATAL_ERROR "windows requires static builds (thanks balmer)") endif() enable_language(RC) option(WITH_WINDOWS_32 "build 32 bit windows" OFF) # unlike unix where you get a *single* compiler ID string in .comment # GNU ld sees fit to merge *all* the .ident sections in object files # to .r[o]data section one after the other! add_compile_options(-fno-ident -Wa,-mbig-obj) function(expand_urls output source_file) set(expanded) foreach(mirror ${ARGN}) list(APPEND expanded "${mirror}/${source_file}") endforeach() set(${output} "${expanded}" PARENT_SCOPE) endfunction() function(add_static_target target ext_target libname) add_library(${target} STATIC IMPORTED GLOBAL) add_dependencies(${target} ${ext_target}) set_target_properties(${target} PROPERTIES IMPORTED_LOCATION ${DEPS_DESTDIR}/lib/${libname} ) endfunction() if(EMBEDDED_CFG) link_libatomic() endif() set(WINTUN_VERSION 0.14.1 CACHE STRING "wintun version") set(WINTUN_MIRROR ${LOCAL_MIRROR} https://www.wintun.net/builds CACHE STRING "wintun mirror(s)") set(WINTUN_SOURCE wintun-${WINTUN_VERSION}.zip) set(WINTUN_HASH SHA256=07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51 CACHE STRING "wintun source hash") set(WINDIVERT_VERSION 2.2.2-A CACHE STRING "windivert version") set(WINDIVERT_MIRROR ${LOCAL_MIRROR} https://reqrypt.org/download CACHE STRING "windivert mirror(s)") set(WINDIVERT_SOURCE WinDivert-${WINDIVERT_VERSION}.zip) set(WINDIVERT_HASH SHA512=92eb2ef98ced175d44de1cdb7c52f2ebc534b6a997926baeb83bfe94cba9287b438f796aff11f6163918bcdbc25bcd4e3383715f139f690d207ce219f846a345 CACHE STRING "windivert source hash") expand_urls(WINTUN_URL ${WINTUN_SOURCE} ${WINTUN_MIRROR}) expand_urls(WINDIVERT_URL ${WINDIVERT_SOURCE} ${WINDIVERT_MIRROR}) message(STATUS "Downloading wintun from ${WINTUN_URL}") file(DOWNLOAD ${WINTUN_URL} ${CMAKE_BINARY_DIR}/wintun.zip EXPECTED_HASH ${WINTUN_HASH}) message(STATUS "Downloading windivert from ${WINDIVERT_URL}") file(DOWNLOAD ${WINDIVERT_URL} ${CMAKE_BINARY_DIR}/windivert.zip EXPECTED_HASH ${WINDIVERT_HASH}) execute_process(COMMAND ${CMAKE_COMMAND} -E tar x ${CMAKE_BINARY_DIR}/wintun.zip WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E tar x ${CMAKE_BINARY_DIR}/windivert.zip WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) ================================================ FILE: cmake/win32_installer_deps.cmake ================================================ install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) if(WITH_WINDOWS_32) install(FILES ${CMAKE_BINARY_DIR}/wintun/bin/x86/wintun.dll DESTINATION bin COMPONENT lokinet) install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x86/WinDivert.sys DESTINATION lib COMPONENT lokinet) install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x86/WinDivert.dll DESTINATION bin COMPONENT lokinet) else() install(FILES ${CMAKE_BINARY_DIR}/wintun/bin/amd64/wintun.dll DESTINATION bin COMPONENT lokinet) install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x64/WinDivert64.sys DESTINATION lib COMPONENT lokinet) install(FILES ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/x64/WinDivert.dll DESTINATION bin COMPONENT lokinet) endif() set(BOOTSTRAP_FILE "${PROJECT_SOURCE_DIR}/contrib/bootstrap/mainnet.signed") install(FILES ${BOOTSTRAP_FILE} DESTINATION share COMPONENT lokinet RENAME bootstrap.signed) set(win_ico "${PROJECT_BINARY_DIR}/lokinet.ico") add_custom_command(OUTPUT "${win_ico}" COMMAND ${PROJECT_SOURCE_DIR}/contrib/make-ico.sh ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg "${win_ico}" DEPENDS ${PROJECT_SOURCE_DIR}/contrib/lokinet.svg ${PROJECT_SOURCE_DIR}/contrib/make-ico.sh) add_custom_target(icon ALL DEPENDS "${win_ico}") set(CPACK_PACKAGE_INSTALL_DIRECTORY "Lokinet") set(CPACK_NSIS_MUI_ICON "${PROJECT_BINARY_DIR}/lokinet.ico") set(CPACK_NSIS_DEFINES "RequestExecutionLevel admin") set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) function(read_nsis_file filename outvar) file(STRINGS "${filename}" _outvar) list(TRANSFORM _outvar REPLACE "\\\\" "\\\\\\\\") list(JOIN _outvar "\\n" out) set(${outvar} ${out} PARENT_SCOPE) endfunction() read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_preinstall.nsis" _extra_preinstall) read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_install.nsis" _extra_install) read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_uninstall.nsis" _extra_uninstall) read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_create_icons.nsis" _extra_create_icons) read_nsis_file("${CMAKE_SOURCE_DIR}/win32-setup/extra_delete_icons.nsis" _extra_delete_icons) set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "${_extra_preinstall}") set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${_extra_install}") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${_extra_uninstall}") set(CPACK_NSIS_CREATE_ICONS_EXTRA "${_extra_create_icons}") set(CPACK_NSIS_DELETE_ICONS_EXTRA "${_extra_delete_icons}") set(CPACK_NSIS_COMPRESSOR "/SOLID lzma") ================================================ FILE: contrib/NetworkManager/dnsmasq/README.md ================================================ Place in `/etc/NetworkManager/dnsmasq.d/lokinet.conf`. To make use of this, first install dnsmasq. Then enable NetworkManager dnsmasq backend by editing `/etc/NetworkManager/NetworkManager.conf` to something like: ``` [main] dns=dnsmasq ``` If NetworkManager is currently running, restart it for changes to take effect: ``` sudo systemctl restart NetworkManager ``` ================================================ FILE: contrib/NetworkManager/dnsmasq/lokinet.conf ================================================ server=/loki/snode/127.3.2.1 ================================================ FILE: contrib/android-configure.sh ================================================ #!/bin/bash set -e default_abis="armeabi-v7a arm64-v8a x86_64" build_abis=${ABIS:-$default_abis} test x$NDK = x && test -e /usr/lib/android-ndk && export NDK=/usr/lib/android-ndk test x$NDK = x && exit 1 echo "building abis: $build_abis" root=$(readlink -f "$1") shift build=$(readlink -f "$1") shift mkdir -p $build cd $build for abi in $build_abis; do mkdir -p build-$abi cd build-$abi cmake \ -S "$root" -B . \ -G 'Unix Makefiles' \ -DANDROID=ON \ -DANDROID_ABI=$abi \ -DANDROID_ARM_MODE=arm \ -DANDROID_PLATFORM=android-23 \ -DANDROID_API=23 \ -DANDROID_STL=c++_static \ -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ -DBUILD_STATIC_DEPS=ON \ -DLOKINET_PACKAGE=ON \ -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TESTING=OFF \ -DLOKINET_TESTS=OFF \ -DLOKINET_BOOTSTRAP=OFF \ -DLOKINET_NATIVE_BUILD=OFF \ -DSTATIC_LINK=ON \ -DWITH_SYSTEMD=OFF \ -DFORCE_OXENMQ_SUBMODULE=ON \ -DFORCE_OXENC_SUBMODULE=ON \ -DFORCE_FMT_SUBMODULE=ON \ -DFORCE_SPDLOG_SUBMODULE=ON \ -DFORCE_NLOHMANN_SUBMODULE=ON \ -DSUBMODULE_CHECK=OFF \ -DWITH_LTO=OFF \ -DCMAKE_BUILD_TYPE=Release \ "$@" cd - done rm -f $build/Makefile echo "# generated makefile" >> $build/Makefile echo "all: $build_abis" >> $build/Makefile for abi in $build_abis; do echo -ne "$abi:\n\t" >> $build/Makefile echo -ne '$(MAKE) -C ' >> $build/Makefile echo "build-$abi lokinet-android" >> $build/Makefile echo -ne "\tmkdir -p out/$abi && cp build-$abi/jni/liblokinet-android.so out/$abi/liblokinet-android.so\n\n" >> $build/Makefile echo -ne "clean-$abi:\n\t" >> $build/Makefile echo -ne '$(MAKE) -C ' >> $build/Makefile echo "build-$abi clean" >> $build/Makefile done echo -ne "clean:" >> $build/Makefile for targ in $build_abis ; do echo -ne " clean-$targ" >> $build/Makefile ; done echo "" >> $build/Makefile ================================================ FILE: contrib/android.sh ================================================ #!/bin/bash set -e set +x root="$(readlink -f $(dirname $0)/../)" cd "$root" ./contrib/android-configure.sh . build-android "$@" make -C build-android -j ${JOBS:-$(nproc)} ================================================ FILE: contrib/apparmor/usr.bin.lokinet ================================================ # Last Modified: Fri 05 Feb 2021 08:13:58 PM UTC #include profile lokinet /usr/bin/lokinet { #include #include capability net_admin, capability net_bind_service, network inet dgram, network inet6 dgram, network netlink raw, /etc/loki/lokinet.ini r, /dev/net/tun rw, /usr/bin/lokinet mr, owner /{var/,}lib/lokinet/ rw, owner /{var/,}lib/lokinet/** rwk, owner ${HOME}/.lokinet/ rw, owner ${HOME}/.lokinet/** rwk, owner @{PROC}/@{pid}/task/@{pid}/comm rw, owner /tmp/lokinet.*/{**,} rw, #include if exists } ================================================ FILE: contrib/apply-patches.sh ================================================ #!/usr/bin/env bash for f in "$@" ; do patch -p1 -i "$f" done ================================================ FILE: contrib/bencode-dump.py ================================================ #!/usr/bin/python3 import sys import pprint if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] == '-'): f = sys.stdin.buffer elif len(sys.argv) != 2 or sys.argv[1].startswith('-'): print("Usage: {} FILE -- dumps a bencoded file".format(sys.argv[0]), file=sys.stderr) sys.exit(1) else: f = open(sys.argv[1], 'rb') initial = f.peek(2) is_hex = False if initial.startswith(b'64') or initial.startswith(b'6c'): print("Input looks like hex bencoded data; parsing as hex input", file=sys.stderr) is_hex = True class HexPrinter(): def __init__(self, data): self.data = data def __repr__(self): return "hex({} bytes):'{}'".format(len(self.data), self.data.hex()) def next_byte(): if is_hex: pair = f.read(2) assert pair is not None and len(pair) == 2 b = int(pair, 16).to_bytes(1, 'big') else: b = f.read(1) assert b is not None and len(b) == 1 return b def parse_int(): s = b'' x = next_byte() while x in b"0123456789-": s += x x = next_byte() assert x == b'e' and len(s) > 0, "Invalid integer encoding" return int(s) def parse_string(s): x = next_byte() while x in b"0123456789": s += x x = next_byte() assert x == b':', "Invalid string encoding" s = int(s) if is_hex: data = bytes.fromhex(f.read(2*s).decode('ascii')) else: data = f.read(s) assert len(data) == s, "Truncated string data" # If the string is ascii then convert to string: if all(0x20 <= b <= 0x7e for b in data): return data.decode() # Otherwise display as hex: return HexPrinter(data) def parse_dict(): d = {} last_key = None while True: t = next_byte() if t == b'e': return d assert t in b"0123456789", "Invalid dict: dict keys must be strings" key = parse_string(t) raw_key = key.data if isinstance(key, HexPrinter) else key.encode() if last_key is not None and raw_key <= last_key: print("Warning: found out-of-order dict keys ({} after {})".format(raw_key, last_key), file=sys.stderr) last_key = raw_key t = next_byte() d[key] = parse_thing(t) def parse_list(): l = [] while True: t = next_byte() if t == b'e': return l l.append(parse_thing(t)) def parse_thing(t): if t == b'd': return parse_dict() if t == b'l': return parse_list() if t == b'i': return parse_int() if t in b"0123456789": return parse_string(t) assert False, "Parsing error: encountered invalid type '{}'".format(t) pprint.PrettyPrinter( indent=2 ).pprint(parse_thing(next_byte())) ================================================ FILE: contrib/bootstrap/make-bootstrap-list.sh ================================================ #!/usr/bin/env bash echo -n 'l' for arg in $@ ; do cat "$arg" ; done echo -n 'e' ================================================ FILE: contrib/bootstrap/readme.txt ================================================ usage: ./make-bootstrap-list.sh $(find $HOME/.lokinet/netdb | grep \\.signed$) > bootstrap.signed ================================================ FILE: contrib/ci/docker/readme.md ================================================ ## drone-ci docker jizz To rebuild all ci images and push them to the oxen registry server do: $ docker login registry.oxen.rocks $ ./rebuild-docker-images.py If you aren't part of the Oxen team, you'll likely need to set up your own registry and change registry.oxen.rocks to your own domain name in order to do anything useful with this. ================================================ FILE: contrib/ci/docker/rebuild-docker-images.py ================================================ #!/usr/bin/env python3 import subprocess import tempfile import optparse import sys from concurrent.futures import ThreadPoolExecutor import threading parser = optparse.OptionParser() parser.add_option("--no-cache", action="store_true", help="Run `docker build` with the `--no-cache` option to ignore existing images") parser.add_option("--parallel", "-j", type="int", default=1, help="Run up to this many builds in parallel") parser.add_option("--distro", type="string", default="", help="Build only this distro; should be DISTRO-CODE or DISTRO-CODE/ARCH, " "e.g. debian-sid/amd64") (options, args) = parser.parse_args() registry_base = 'registry.oxen.rocks/lokinet-ci-' distros = [*(('debian', x) for x in ('sid', 'stable', 'testing', 'bullseye', 'buster')), *(('ubuntu', x) for x in ('rolling', 'lts', 'impish', 'hirsute', 'focal', 'bionic'))] if options.distro: d = options.distro.split('-') if len(d) != 2 or d[0] not in ('debian', 'ubuntu') or not d[1]: print("Bad --distro value '{}'".format(options.distro), file=sys.stderr) sys.exit(1) distros = [(d[0], d[1].split('/')[0])] manifests = {} # "image:latest": ["image/amd64", "image/arm64v8", ...] manifestlock = threading.Lock() def arches(distro): if options.distro and '/' in options.distro: arch = options.distro.split('/') if arch not in ('amd64', 'i386', 'arm64v8', 'arm32v7'): print("Bad --distro value '{}'".format(options.distro), file=sys.stderr) sys.exit(1) return [arch] a = ['amd64', 'arm64v8', 'arm32v7'] if distro[0] == 'debian' or distro == ('ubuntu', 'bionic'): a.append('i386') # i386 builds don't work on later ubuntu return a hacks = { registry_base + 'ubuntu-bionic-builder': """g++-8 \ && mkdir -p /usr/lib/x86_64-linux-gnu/pgm-5.2/include""", } failure = False lineno = 0 linelock = threading.Lock() def print_line(myline, value): linelock.acquire() global lineno if sys.__stdout__.isatty(): jump = lineno - myline print("\033[{jump}A\r\033[K{value}\033[{jump}B\r".format(jump=jump, value=value), end='') sys.stdout.flush() else: print(value) linelock.release() def run_or_report(*args, myline): try: subprocess.run( args, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8') except subprocess.CalledProcessError as e: with tempfile.NamedTemporaryFile(suffix=".log", delete=False) as log: log.write("Error running {}: {}\n\nOutput:\n\n".format(' '.join(args), e).encode()) log.write(e.output.encode()) global failure failure = True print_line(myline, "\033[31;1mError! See {} for details".format(log.name)) raise e def build_tag(tag_base, arch, contents): if failure: raise ChildProcessError() linelock.acquire() global lineno myline = lineno lineno += 1 print() linelock.release() with tempfile.NamedTemporaryFile() as dockerfile: dockerfile.write(contents.encode()) dockerfile.flush() tag = '{}/{}'.format(tag_base, arch) print_line(myline, "\033[33;1mRebuilding \033[35;1m{}\033[0m".format(tag)) run_or_report('docker', 'build', '--pull', '-f', dockerfile.name, '-t', tag, *(('--no-cache',) if options.no_cache else ()), '.', myline=myline) print_line(myline, "\033[33;1mPushing \033[35;1m{}\033[0m".format(tag)) run_or_report('docker', 'push', tag, myline=myline) print_line(myline, "\033[32;1mFinished build \033[35;1m{}\033[0m".format(tag)) latest = tag_base + ':latest' global manifests manifestlock.acquire() if latest in manifests: manifests[latest].append(tag) else: manifests[latest] = [tag] manifestlock.release() def base_distro_build(distro, arch): tag = '{r}{distro[0]}-{distro[1]}-base'.format(r=registry_base, distro=distro) codename = 'latest' if distro == ('ubuntu', 'lts') else distro[1] build_tag(tag, arch, """ FROM {}/{}:{} RUN /bin/bash -c 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections' RUN apt-get -o=Dpkg::Use-Pty=0 -q update \ && apt-get -o=Dpkg::Use-Pty=0 -q dist-upgrade -y \ && apt-get -o=Dpkg::Use-Pty=0 -q autoremove -y \ {hacks} """.format(arch, distro[0], codename, hacks=hacks.get(tag, ''))) def distro_build(distro, arch): prefix = '{r}{distro[0]}-{distro[1]}'.format(r=registry_base, distro=distro) fmtargs = dict(arch=arch, distro=distro, prefix=prefix) # (distro)-(codename)-base: Base image from upstream: we sync the repos, but do nothing else. if (distro, arch) != (('debian', 'stable'), 'amd64'): # debian-stable-base/amd64 already built base_distro_build(distro, arch) # (distro)-(codename)-builder: Deb builder image used for building debs; we add the basic tools # we use to build debs, not including things that should come from the dependencies in the # debian/control file. build_tag(prefix + '-builder', arch, """ FROM {prefix}-base/{arch} RUN apt-get -o=Dpkg::Use-Pty=0 -q update \ && apt-get -o=Dpkg::Use-Pty=0 -q dist-upgrade -y \ && apt-get -o=Dpkg::Use-Pty=0 --no-install-recommends -q install -y \ ccache \ devscripts \ equivs \ g++ \ git \ git-buildpackage \ openssh-client \ {hacks} """.format(**fmtargs, hacks=hacks.get(prefix + '-builder', ''))) # (distro)-(codename): Basic image we use for most builds. This takes the -builder and adds # most dependencies found in our packages. build_tag(prefix, arch, """ FROM {prefix}-builder/{arch} RUN apt-get -o=Dpkg::Use-Pty=0 -q update \ && apt-get -o=Dpkg::Use-Pty=0 -q dist-upgrade -y \ && apt-get -o=Dpkg::Use-Pty=0 --no-install-recommends -q install -y \ automake \ ccache \ cmake \ eatmydata \ g++ \ gdb \ git \ libboost-program-options-dev \ libboost-serialization-dev \ libboost-thread-dev \ libcurl4-openssl-dev \ libevent-dev \ libgtest-dev \ libhidapi-dev \ libjemalloc-dev \ libminiupnpc-dev \ libreadline-dev \ libsodium-dev \ libsqlite3-dev \ libssl-dev \ libsystemd-dev \ libtool \ libunbound-dev \ libunwind8-dev \ libusb-1.0.0-dev \ libuv1-dev \ libzmq3-dev \ lsb-release \ make \ nettle-dev \ ninja-build \ openssh-client \ patch \ pkg-config \ pybind11-dev \ python3-dev \ python3-pip \ python3-pybind11 \ python3-pytest \ python3-setuptools \ qttools5-dev \ {hacks} """.format(**fmtargs, hacks=hacks.get(prefix, ''))) # Android and flutter builds on top of debian-stable-base and adds a ton of android crap; we # schedule this job as soon as the debian-sid-base/amd64 build finishes, because they easily take # the longest and are by far the biggest images. def android_builds(): build_tag(registry_base + 'android', 'amd64', """ FROM {r}debian-stable-base RUN /bin/bash -c 'sed -i "s/main/main contrib/g" /etc/apt/sources.list' RUN apt-get -o=Dpkg::Use-Pty=0 -q update \ && apt-get -o=Dpkg::Use-Pty=0 -q dist-upgrade -y \ && apt-get -o=Dpkg::Use-Pty=0 -q install --no-install-recommends -y \ android-sdk \ automake \ ccache \ cmake \ curl \ git \ google-android-ndk-installer \ libtool \ make \ openssh-client \ patch \ pkg-config \ wget \ xz-utils \ zip \ && git clone https://github.com/Shadowstyler/android-sdk-licenses.git /tmp/android-sdk-licenses \ && cp -a /tmp/android-sdk-licenses/*-license /usr/lib/android-sdk/licenses \ && rm -rf /tmp/android-sdk-licenses """.format(r=registry_base)) build_tag(registry_base + 'flutter', 'amd64', """ FROM {r}android RUN cd /opt \ && curl https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_2.2.2-stable.tar.xz \ | tar xJv \ && ln -s /opt/flutter/bin/flutter /usr/local/bin/ \ && flutter precache """.format(r=registry_base)) # lint is a tiny build (on top of debian-stable-base) with just formatting checking tools def lint_build(): build_tag(registry_base + 'lint', 'amd64', """ FROM {r}debian-stable-base RUN apt-get -o=Dpkg::Use-Pty=0 -q install --no-install-recommends -y \ clang-format-11 \ eatmydata \ git \ jsonnet """.format(r=registry_base)) def nodejs_build(): build_tag(registry_base + 'nodejs', 'amd64', """ FROM node:14.16.1 RUN /bin/bash -c 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections' RUN apt-get -o=Dpkg::Use-Pty=0 -q update \ && apt-get -o=Dpkg::Use-Pty=0 -q dist-upgrade -y \ && apt-get -o=Dpkg::Use-Pty=0 -q install --no-install-recommends -y \ ccache \ cmake \ eatmydata \ g++ \ gdb \ git \ make \ ninja-build \ openssh-client \ patch \ pkg-config \ wine """) # Start debian-stable-base/amd64 on its own, because other builds depend on it and we want to get # those (especially android/flutter) fired off as soon as possible (because it's slow and huge). if ('debian', 'stable') in distros: base_distro_build(['debian', 'stable'], 'amd64') executor = ThreadPoolExecutor(max_workers=max(options.parallel, 1)) if options.distro: jobs = [] else: jobs = [executor.submit(b) for b in (android_builds, lint_build, nodejs_build)] for d in distros: for a in arches(d): jobs.append(executor.submit(distro_build, d, a)) while len(jobs): j = jobs.pop(0) try: j.result() except (ChildProcessError, subprocess.CalledProcessError): for k in jobs: k.cancel() if failure: print("Error(s) occured, aborting!", file=sys.stderr) sys.exit(1) print("\n\n\033[32;1mAll builds finished successfully; pushing manifests...\033[0m\n") def push_manifest(latest, tags): if failure: raise ChildProcessError() linelock.acquire() global lineno myline = lineno lineno += 1 print() linelock.release() subprocess.run(['docker', 'manifest', 'rm', latest], stderr=subprocess.DEVNULL, check=False) print_line(myline, "\033[33;1mCreating manifest \033[35;1m{}\033[0m".format(latest)) run_or_report('docker', 'manifest', 'create', latest, *tags, myline=myline) print_line(myline, "\033[33;1mPushing manifest \033[35;1m{}\033[0m".format(latest)) run_or_report('docker', 'manifest', 'push', latest, myline=myline) print_line(myline, "\033[32;1mFinished manifest \033[35;1m{}\033[0m".format(latest)) for latest, tags in manifests.items(): jobs.append(executor.submit(push_manifest, latest, tags)) while len(jobs): j = jobs.pop(0) try: j.result() except (ChildProcessError, subprocess.CalledProcessError): for k in jobs: k.cancel() print("\n\n\033[32;1mAll done!\n") ================================================ FILE: contrib/ci/drone-check-static-libs.sh ================================================ #!/usr/bin/env bash # Script used with Drone CI to check that a statically build lokinet only links against the expected # base system libraries. Expects to be run with pwd of the project directory with a build in # `build` or $1 (if given). set -o errexit build=${1:-build} bad= if [ "$DRONE_STAGE_OS" == "darwin" ]; then if otool -L ${build}/llarp/apple/org.lokinet.network-extension.systemextension/Contents/MacOS/org.lokinet.network-extension | \ grep -Ev '^llarp/apple:|^\t(/usr/lib/lib(System\.|c\+\+|objc))|/System/Library/Frameworks/(CoreFoundation|NetworkExtension|Foundation|Network)\.framework'; then bad=1 fi elif [ "$DRONE_STAGE_OS" == "linux" ]; then if ldd ${build}/daemon/lokinet | grep -Ev '(linux-vdso|ld-linux-(x86-64|armhf|aarch64)|lib(pthread|dl|rt|stdc\+\+|gcc_s|c|m))\.so'; then bad=1 fi else echo -e "\n\n\n\n\e[31;1mDon't know how to check linked libs on $DRONE_STAGE_OS\e[0m\n\n\n" exit 1 fi if [ -n "$bad" ]; then echo -e "\n\n\n\n\e[31;1mlokinet links to unexpected libraries\e[0m\n\n\n" exit 1 fi echo -e "\n\n\n\n\e[32;1mNo unexpected linked libraries found\e[0m\n\n\n" ================================================ FILE: contrib/ci/drone-debs-upload.sh ================================================ #!/bin/bash # Script used with Drone CI to upload debs from the deb building pipelines (because specifying all # this in .drone.jsonnet is too painful). This is expected to run from the base project dir after # having build with debuild (which will leave the debs in ..). set -o errexit distro="$1" if [ -z "$distro" ]; then echo "Bad usage: need distro name as first argument" exit 1 fi if [ -z "$SSH_KEY" ]; then echo -e "\n\n\n\e[31;1mUnable to upload debs: SSH_KEY not set\e[0m" # Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds exit 0 fi echo "$SSH_KEY" >~/ssh_key set -o xtrace # Don't start tracing until *after* we write the ssh key chmod 600 ~/ssh_key upload_to="oxen.rocks/debs/${DRONE_REPO// /_}@${DRONE_BRANCH// /_}/$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}/$distro/$DRONE_STAGE_ARCH" # sftp doesn't have any equivalent to mkdir -p, so we have to split the above up into a chain of # -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail # without error. upload_dirs=(${upload_to//\// }) mkdirs= dir_tmp="" for p in "${upload_dirs[@]}"; do dir_tmp="$dir_tmp$p/" mkdirs="$mkdirs -mkdir $dir_tmp" done sftp -i ~/ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks <ssh_key set -o xtrace # Don't start tracing until *after* we write the ssh key chmod 600 ssh_key os="$UPLOAD_OS" if [ -z "$os" ]; then if [ "$DRONE_STAGE_OS" == "darwin" ]; then os="macos-$DRONE_STAGE_ARCH" elif [ -n "$WINDOWS_BUILD_NAME" ]; then os="windows-$WINDOWS_BUILD_NAME" else os="$DRONE_STAGE_OS-$DRONE_STAGE_ARCH" fi fi if [ -n "$DRONE_TAG" ]; then # For a tag build use something like `lokinet-linux-amd64-v1.2.3` base="lokinet-$os-$DRONE_TAG" else # Otherwise build a length name from the datetime and commit hash, such as: # lokinet-linux-amd64-20200522T212342Z-04d7dcc54 base="lokinet-$os-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}" fi mkdir -v "$base" if [ -e build/win32 ]; then # save debug symbols cp -av build/win32/daemon/debug-symbols.tar.xz "$base-debug-symbols.tar.xz" # save installer cp -av build/win32/*.exe "$base" # zipit up yo archive="$base.zip" zip -r "$archive" "$base" elif [ -e lokinet.apk ] ; then # android af ngl archive="$base.apk" cp -av lokinet.apk "$archive" elif [ -e build-docs ]; then archive="$base.tar.xz" cp -av build-docs/docs/mkdocs.yml build-docs/docs/markdown "$base" tar cJvf "$archive" "$base" elif [ -e build-mac ]; then archive="$base.tar.xz" mv build-mac/Lokinet*/ "$base" tar cJvf "$archive" "$base" else cp -av build/daemon/lokinet{,-cntrl} "$base" cp -av contrib/bootstrap/mainnet.signed "$base/bootstrap.signed" # tar dat shiz up yo archive="$base.tar.xz" tar cJvf "$archive" "$base" fi upload_to="oxen.rocks/${DRONE_REPO// /_}/${DRONE_BRANCH// /_}" # sftp doesn't have any equivalent to mkdir -p, so we have to split the above up into a chain of # -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail # without error. upload_dirs=(${upload_to//\// }) put_debug= mkdirs= dir_tmp="" for p in "${upload_dirs[@]}"; do dir_tmp="$dir_tmp$p/" mkdirs="$mkdirs -mkdir $dir_tmp" done if [ -e "$base-debug-symbols.tar.xz" ] ; then put_debug="put $base-debug-symbols.tar.xz $upload_to" fi sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks < build-cross/Makefile for arch in $archs ; do mkdir -p $root/build-cross/build-$arch cd $root/build-cross/build-$arch cmake \ -G 'Unix Makefiles' \ -DCROSS_PLATFORM=$platform \ -DCROSS_PREFIX=$arch \ -DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \ -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always \ -DCMAKE_TOOLCHAIN_FILE=$root/contrib/cross/cross.toolchain.cmake \ -DBUILD_STATIC_DEPS=ON \ -DSTATIC_LINK=ON \ -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TESTING=OFF \ -DBUILD_LIBLOKINET=OFF \ -DLOKINET_TESTS=OFF \ -DLOKINET_NATIVE_BUILD=OFF \ -DSTATIC_LINK=ON \ -DWITH_SYSTEMD=OFF \ -DFORCE_OXENMQ_SUBMODULE=ON \ -DSUBMODULE_CHECK=OFF \ -DWITH_LTO=OFF \ -DLOKINET_BOOTSTRAP=OFF \ -DCMAKE_BUILD_TYPE=RelWithDeb \ "${cmake_extra[@]}" \ $root cd $root/build-cross echo -ne "$arch:\n\t\$(MAKE) -C build-$arch\n" >> $root/build-cross/Makefile done cd $root make -j${JOBS:-$(nproc)} -C build-cross ================================================ FILE: contrib/format-version.sh ================================================ CLANG_FORMAT_DESIRED_VERSION=19 CLANG_FORMAT=$(command -v clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) if [ $? -ne 0 ]; then CLANG_FORMAT=$(command -v clang-format-mp-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) fi if [ $? -ne 0 ]; then CLANG_FORMAT=$(command -v clang-format 2>/dev/null) if [ $? -ne 0 ]; then echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." exit 1 fi version=$(clang-format --version) if [[ ! $version == *"clang-format version $CLANG_FORMAT_DESIRED_VERSION"* ]]; then echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." exit 1 fi fi ================================================ FILE: contrib/format.sh ================================================ #!/usr/bin/env bash # set -x set -e . $(dirname $0)/format-version.sh cd "$(dirname $0)/../" sources=($(find jni daemon llarp include pybind | grep -E '\.([hc](pp)?|m(m)?)$' | grep -v '#')) incl_pat='^(#include +)"(llarp|libntrup|oxen|oxenc|oxenmq|quic|CLI|cpr|nlohmann|ghc|fmt|spdlog|uvw?)([/.][^"]*)"' if [ "$1" = "verify" ] ; then if [ $($CLANG_FORMAT --output-replacements-xml "${sources[@]}" | grep '' | wc -l) -ne 0 ] ; then exit 2 fi if grep --color -E "$incl_pat" "${sources[@]}"; then exit 5 fi else $CLANG_FORMAT -i "${sources[@]}" &> /dev/null perl -pi -e "s{$incl_pat}"'{$1<$2$3>}' "${sources[@]}" &> /dev/null fi # Some includes just shouldn't exist anywhere, but need to be fixed manually: if grep --color -E '^#include ([<"]external/|/dev/null) if [ $? -eq 0 ]; then if [ "$1" = "verify" ] ; then for f in $(find daemon | grep -E '\.swift$' | grep -v '#') ; do if [ $($swift_format --quiet --dryrun < "$f" | diff "$f" - | wc -l) -ne 0 ] ; then exit 3 fi done else $swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '#') fi fi jsonnet_format=$(command -v jsonnetfmt 2>/dev/null) if [ $? -eq 0 ]; then if [ "$1" = "verify" ]; then if ! $jsonnet_format --test .drone.jsonnet; then exit 4 fi else $jsonnet_format --in-place .drone.jsonnet fi fi ================================================ FILE: contrib/git-hook-pre-push.sh ================================================ #!/bin/bash # # pre-push hook for git # this script is probably overkill for most contributors # # "i use this to prevent foot cannons caused by commiting broken code" # # ~ jeff (lokinet author and crazy person) # # # to use this as a git hook do this in the root of the repo: # # cp contrib/git-hook-pre-push.sh .git/hooks/pre-push # set -e cd "$(dirname $0)/../.." echo "check format..." ./contrib/format.sh verify echo "format is gucci af fam" echo "remove old test build directory..." rm -rf build-git-hook mkdir build-git-hook echo "configuring test build jizz..." cmake -S . -B build-git-hook -DWITH_LTO=OFF -DLOKINET_HIVE=ON -G Ninja echo "ensure this shit compiles..." ninja -C build-git-hook all echo "ensure unit tests aren't fucked..." ninja -C build-git-hook check echo "we gud UmU" echo "" ================================================ FILE: contrib/hex-to-base32z.py ================================================ #!/usr/bin/python3 import sys base32z_dict = 'ybndrfg8ejkmcpqxot1uwisza345h769' base32z_map = {base32z_dict[i]: i for i in range(len(base32z_dict))} def lokinet_snode_addr(pk_hex): """Returns the lokinet snode address from a hex ed25519 pubkey""" assert(len(pk_hex) == 64) bits = 0 val = 0 result = '' for x in pk_hex: bits += 4 val = (val << 4) + int(x, 16) if bits >= 5: bits -= 5 v = val >> bits val &= (1 << bits) - 1 result += base32z_dict[v] result += base32z_dict[val << (5 - bits)] return result + ".snode" def hex_from_snode(b32z): """undoes what the above does; b32z should have '.snode' already stripped off""" assert(len(b32z) == 52) val = 0 bits = 0 for x in b32z: val = (val << 5) | base32z_map[x] # Arbitrary precision integers FTW # `val` is now a 260 bit value (52 * 5 bits per char); but we only use the first bit of the last # value (which is why lokinet addresses always end with y or o) assert(b32z[-1] in 'yo') val >>= 4 return "{:64x}".format(val) reverse = False if len(sys.argv) >= 2 and sys.argv[1] == '-r': reverse = True del sys.argv[1] if len(sys.argv) < 2 or ( any(len(x) not in (52, 58) for x in sys.argv[1:]) if reverse else any(len(x) != 64 for x in sys.argv[1:]) ): print("Usage: {} PUBKEY [PUBKEY ...] -- converts ed25519 pubkeys to .snode addresses".format(sys.argv[0])) print("Usage: {} -r SNODE [SNODE ...] -- converts snode addresses to ed25519 pubkeys".format(sys.argv[0])) sys.exit(1) if reverse: for key in sys.argv[1:]: print("{}.snode -> {}".format(key[0:52], hex_from_snode(key[0:52]))) else: for key in sys.argv[1:]: print("{} -> {}".format(key, lokinet_snode_addr(key))) ================================================ FILE: contrib/keygen.py ================================================ #!/usr/bin/env python3 # # .loki secret key generator script # makes keyfile contents # # usage: python3 keygen.py out.private # python3 keygen.py > /some/where/over/the/rainbow # from nacl.bindings import crypto_sign_keypair import sys out = sys.stdout close_out = lambda : None args = sys.argv[1:] if args and args[0] != '-': out = open(args[0], 'wb') close_out = out.close pk, sk = crypto_sign_keypair() out.write(b'64:') out.write(sk) out.flush() close_out() ================================================ FILE: contrib/liblokinet/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(udptest LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) add_executable(udptest udptest.cpp) include_directories(../../include) target_link_libraries(udptest PUBLIC lokinet) ================================================ FILE: contrib/liblokinet/readme.md ================================================ # liblokinet examples building: $ mkdir -p build $ cd build $ cp /path/to/liblokinet.so . $ cmake .. -DCMAKE_EXE_LINKER_FLAGS='-L.' $ make running: $ ./udptest /path/to/bootstrap.signed ================================================ FILE: contrib/liblokinet/udptest.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include bool _run{true}; using Lokinet_ptr = std::shared_ptr; [[nodiscard]] auto MakeLokinet(const std::vector& bootstrap) { auto ctx = std::shared_ptr(lokinet_context_new(), lokinet_context_free); if (auto err = lokinet_add_bootstrap_rc(bootstrap.data(), bootstrap.size(), ctx.get())) throw std::runtime_error{strerror(err)}; if (lokinet_context_start(ctx.get())) throw std::runtime_error{"could not start context"}; return ctx; } void WaitForReady(const Lokinet_ptr& ctx) { while (_run and lokinet_wait_for_ready(1000, ctx.get())) { std::cout << "waiting for context..." << std::endl; } } class Flow { lokinet_udp_flowinfo const _info; lokinet_context* const _ctx; public: explicit Flow(const lokinet_udp_flowinfo* info, lokinet_context* ctx) : _info{*info}, _ctx{ctx} {} lokinet_context* Context() const { return _ctx; } std::string String() const { std::stringstream ss; ss << std::string{_info.remote_host} << ":" << std::to_string(_info.remote_port) << " on socket " << _info.socket_id; return ss.str(); } }; struct ConnectJob { lokinet_udp_flowinfo remote; lokinet_context* ctx; }; void CreateOutboundFlow(void* user, void** flowdata, int* timeout) { auto* job = static_cast(user); Flow* flow = new Flow{&job->remote, job->ctx}; *flowdata = flow; *timeout = 30; std::cout << "made outbound flow: " << flow->String() << std::endl; ; } int ProcessNewInboundFlow(void* user, const lokinet_udp_flowinfo* remote, void** flowdata, int* timeout) { auto* ctx = static_cast(user); Flow* flow = new Flow{remote, ctx}; std::cout << "new udp flow: " << flow->String() << std::endl; *flowdata = flow; *timeout = 30; return 0; } void DeleteFlow(const lokinet_udp_flowinfo* remote, void* flowdata) { auto* flow = static_cast(flowdata); std::cout << "udp flow from " << flow->String() << " timed out" << std::endl; delete flow; } void HandleUDPPacket(const lokinet_udp_flowinfo* remote, const char* pkt, size_t len, void* flowdata) { auto* flow = static_cast(flowdata); std::cout << "we got " << len << " bytes of udp from " << flow->String() << std::endl; } void BounceUDPPacket(const lokinet_udp_flowinfo* remote, const char* pkt, size_t len, void* flowdata) { auto* flow = static_cast(flowdata); std::cout << "bounce " << len << " bytes of udp from " << flow->String() << std::endl; if (auto err = lokinet_udp_flow_send(remote, pkt, len, flow->Context())) { std::cout << "bounce failed: " << strerror(err) << std::endl; } } Lokinet_ptr sender, recip; void signal_handler(int) { _run = false; } int main(int argc, char* argv[]) { if (argc == 1) { std::cout << "usage: " << argv[0] << " bootstrap.signed" << std::endl; return 1; } /* signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); */ std::vector bootstrap; // load bootstrap.signed { std::ifstream inf{argv[1], std::ifstream::ate | std::ifstream::binary}; size_t len = inf.tellg(); inf.seekg(0); bootstrap.resize(len); inf.read(bootstrap.data(), bootstrap.size()); } if (auto* loglevel = getenv("LOKINET_LOG")) lokinet_log_level(loglevel); else lokinet_log_level("none"); std::cout << "starting up" << std::endl; recip = MakeLokinet(bootstrap); WaitForReady(recip); lokinet_udp_bind_result recipBindResult{}; const auto port = 10000; if (auto err = lokinet_udp_bind( port, ProcessNewInboundFlow, BounceUDPPacket, DeleteFlow, recip.get(), &recipBindResult, recip.get())) { std::cout << "failed to bind recip udp socket " << strerror(err) << std::endl; return 0; } std::cout << "bound recip udp" << std::endl; sender = MakeLokinet(bootstrap); WaitForReady(sender); std::string recipaddr{lokinet_address(recip.get())}; std::cout << "recip ready at " << recipaddr << std::endl; lokinet_udp_bind_result senderBindResult{}; if (auto err = lokinet_udp_bind( port, ProcessNewInboundFlow, HandleUDPPacket, DeleteFlow, sender.get(), &senderBindResult, sender.get())) { std::cout << "failed to bind sender udp socket " << strerror(err) << std::endl; return 0; } ConnectJob connect{}; connect.remote.socket_id = senderBindResult.socket_id; connect.remote.remote_port = port; std::copy_n(recipaddr.c_str(), recipaddr.size(), connect.remote.remote_host); connect.ctx = sender.get(); std::cout << "bound sender udp" << std::endl; do { std::cout << "try establish to " << connect.remote.remote_host << std::endl; if (auto err = lokinet_udp_establish(CreateOutboundFlow, &connect, &connect.remote, sender.get())) { std::cout << "failed to establish to recip: " << strerror(err) << std::endl; usleep(100000); } else break; } while (true); std::cout << "sender established" << std::endl; const std::string buf{"liblokinet"}; const std::string senderAddr{lokinet_address(sender.get())}; do { std::cout << senderAddr << " send to remote: " << buf << std::endl; if (auto err = lokinet_udp_flow_send(&connect.remote, buf.data(), buf.size(), sender.get())) { std::cout << "send failed: " << strerror(err) << std::endl; } usleep(100000); } while (_run); return 0; } ================================================ FILE: contrib/liblokinet_jank_test.cpp ================================================ #include #include #include #include #include #include using namespace std::literals; int main(int argc, char** argv) { if (argc <= 1) { std::cerr << "USAGE: " << argv[0] << " {WHATEVER.loki | WHATEVER.snode}\n"; return 1; } std::string target{argv[1]}; lokinet::Lokinet loki{std::filesystem::path{"lokinet.ini"}}; std::promise prom; loki.on_connected([&] { std::cout << "\n\x1b[32;1mLokinet connected!\x1b[0m\n\n\x1b[33;1mINITIATING SESSION TO " << target << "\x1b[0m\n\n" << std::flush; loki.establish_udp( target, 12345, [](auto udp_info) { std::cout << "\n\x1b[32;1mUDP bound to port " << udp_info.local_port << "\x1b[0m\n\n" << std::flush; }, [&prom](std::string_view fail_msg) { try { throw std::runtime_error{std::string{fail_msg}}; } catch (...) { prom.set_exception(std::current_exception()); } }); }); try { prom.get_future().get(); } catch (const std::exception& e) { std::cerr << "\n\n\x1b[31;1mError establishing session to " << target << ": " << e.what() << "\x1b[0m\n\n"; return 1; } /* loki.map_tcp_remote_port(std::string{argv[1]}, 12345, [&](auto tunnel_info) { std::cout << "\n\nTCP bound to port " << tunnel_info.local_port << "\n\n"; }, [&](auto error_str) { std::cerr << "\nTCP Tunnel map error: " << error_str << "\n"; }); */ std::cout << "\nPRESS ENTER TO EXIT\n"; std::string ignored; std::getline(std::cin, ignored); std::cout << "\nEXITING\n"; } ================================================ FILE: contrib/lokinet-resolvconf ================================================ #!/bin/bash # Script to invoke resolvconf (if installed) to add/remove lokinet into/from the resolvconf DNS # server list. This script does not add if any of these are true: # # - /sbin/resolvconf does not exist # - the systemd-resolved service is active # - a `no-resolvconf=1` item is present in the [dns] section of lokinet.ini # # It always attempts to remove if resolvconf is installed (so that commenting out while running, # then stopping still removes the added entry). # # Usage: lokinet-resolvconf {add|remove} /etc/loki/lokinet.ini set -e action="$1" conf="$2" if [[ ! ("$action" == "add" || "$action" == "remove") || ! -f "$conf" ]]; then echo "Usage: $0 {add|remove} /path/to/lokinet.ini" >&2 exit 1 fi if ! [ -x /sbin/resolvconf ]; then exit 0 fi if [ -x /bin/systemctl ] && /bin/systemctl --quiet is-active systemd-resolved.service; then exit 0 fi if [ "$action" == "add" ]; then if ! [ -x /sbin/resolvconf ]; then exit 0; fi lokinet_ns=$(perl -e ' $ns = "127.3.2.1"; # default if none found in .ini while (<>) { if ((/^\[dns\]/ ... /^\[/)) { if (/^bind\s*=\s*([\d.]+)(?::53)?\s*$/) { $ns=$1; } elsif (/^no-resolvconf\s*=\s*1\s*/) { exit; } } } print $ns' "$conf") if [ -n "$lokinet_ns" ]; then echo "nameserver $lokinet_ns" | /sbin/resolvconf -a lo.000lokinet fi else /sbin/resolvconf -d lo.000lokinet fi ================================================ FILE: contrib/mac-configure.sh ================================================ #!/bin/bash set -e set -x if ! [ -f LICENSE ] || ! [ -d llarp ]; then echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory" >&2 exit 1 fi mkdir -p build-mac cd build-mac cmake \ -G Ninja \ -DBUILD_STATIC_DEPS=ON \ -DLOKINET_TESTS=OFF \ -DLOKINET_BOOTSTRAP=OFF \ -DLOKINET_NATIVE_BUILD=OFF \ -DWITH_LTO=ON \ -DCMAKE_BUILD_TYPE=Release \ -DMACOS_SYSTEM_EXTENSION=ON \ -DCODESIGN=ON \ -DLOKINET_PACKAGE=ON \ "$@" \ .. echo "cmake build configured in build-mac" ================================================ FILE: contrib/mac.sh ================================================ #!/bin/bash # # Build the shit on mac # # You will generally need to add: -DCODESIGN_APP=... to make this work, and (unless you are a # lokinet team member) will need to pay Apple money for your own team ID and arse around with # provisioning profiles. See macos/README.txt. # set -e set -x if ! [ -f LICENSE ] || ! [ -d llarp ]; then echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory" >&2 exit 1 fi ./contrib/mac-configure.sh "$@" cd build-mac rm -rf Lokinet\ * ninja -j${JOBS:-1} dmg cd .. echo -e "Build complete, your app is here:\n" ls -lad $(pwd)/build-mac/Lokinet\ * echo "" ================================================ FILE: contrib/macos/lokinet-extension.Info.plist.in ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName Lokinet Network Extension CFBundleExecutable org.lokinet.network-extension CFBundleIdentifier org.lokinet.network-extension CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType SYSX CFBundleName org.lokinet.network-extension CFBundleVersion @lokinet_VERSION@.@LOKINET_APPLE_BUILD@ CFBundleShortVersionString @lokinet_VERSION@ CFBundleSupportedPlatforms MacOSX ITSAppUsesNonExemptEncryption LSMinimumSystemVersion 10.15 NSHumanReadableCopyright Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later NSSystemExtensionUsageDescription Provides Lokinet Network connectivity. NetworkExtension NEMachServiceName SUQ8J2PCT7.org.lokinet.network-extension NEProviderClasses com.apple.networkextension.packet-tunnel LLARPPacketTunnel com.apple.networkextension.dns-proxy LLARPDNSProxy ================================================ FILE: contrib/macos/lokinet-extension.plugin.entitlements.plist ================================================ com.apple.application-identifier SUQ8J2PCT7.org.lokinet.network-extension com.apple.developer.networking.networkextension packet-tunnel-provider dns-proxy com.apple.developer.team-identifier SUQ8J2PCT7 com.apple.security.app-sandbox com.apple.security.network.client com.apple.security.network.server ================================================ FILE: contrib/macos/lokinet-extension.sysext.entitlements.plist ================================================ com.apple.application-identifier SUQ8J2PCT7.org.lokinet.network-extension com.apple.developer.networking.networkextension packet-tunnel-provider-systemextension dns-proxy-systemextension com.apple.developer.team-identifier SUQ8J2PCT7 com.apple.security.app-sandbox com.apple.security.application-groups SUQ8J2PCT7.org.lokinet com.apple.security.network.client com.apple.security.network.server ================================================ FILE: contrib/macos/lokinet-newsyslog.conf ================================================ /var/log/lokinet.log 644 5 5M $D0 J ================================================ FILE: contrib/macos/lokinet.Info.plist.in ================================================ CFBundleDevelopmentRegion en CFBundleExecutable Lokinet CFBundleIdentifier org.lokinet CFBundleInfoDictionaryVersion 6.0 CFBundleName Lokinet CFBundleIconFile icon.icns CFBundlePackageType APPL CFBundleShortVersionString @lokinet_VERSION@ CFBundleVersion @lokinet_VERSION@.@LOKINET_APPLE_BUILD@ LSMinimumSystemVersion 10.15 NSHumanReadableCopyright Copyright © 2022 The Oxen Project, licensed under GPLv3-or-later LSUIElement LSHasLocalizedDisplayName ================================================ FILE: contrib/macos/lokinet.plugin.entitlements.plist ================================================ com.apple.application-identifier SUQ8J2PCT7.org.lokinet com.apple.developer.networking.networkextension packet-tunnel-provider dns-proxy dns-settings com.apple.developer.team-identifier SUQ8J2PCT7 com.apple.security.app-sandbox com.apple.security.network.client com.apple.security.network.server ================================================ FILE: contrib/macos/lokinet.sysext.entitlements.plist ================================================ com.apple.application-identifier SUQ8J2PCT7.org.lokinet com.apple.developer.networking.networkextension packet-tunnel-provider-systemextension dns-proxy-systemextension dns-settings com.apple.developer.team-identifier SUQ8J2PCT7 com.apple.developer.system-extension.install com.apple.security.app-sandbox com.apple.security.application-groups SUQ8J2PCT7.org.lokinet com.apple.security.network.client com.apple.security.network.server ================================================ FILE: contrib/macos/mk-icns.sh ================================================ #!/bin/bash # Invoked from cmake as mk-icns.sh /path/to/icon.svg /path/to/output.icns svg="$1" out="$2" outdir="${out/%.icns/.iconset}" set -e # Apple's PNG encoding/decoding is buggy and likes to inject yellow lines, particularly for the # smaller images. This is apparently a known issue since macOS 11 that apple just doesn't give a # shit about fixing (https://en.wikipedia.org/wiki/Apple_Icon_Image_format#Known_issues). # # So moral of the story: we have to arse around and edit the png to put a tranparent pixel in the # bottom-left corner but that pixel *must* be different from the preceeding color, otherwise Apple's # garbage breaks exposing the dumpster fire that lies beneath and drops the blue channel from the # last pixel (or run of pixels, if they are the same color (ignoring transparency). So, just to be # consistent, we make *all* 4 corners transparent yellow, because it seems unlikely for our logo to # have full-on yellow in the corner, and the color itself is irrelevant because it is fully # transparent. # # Why is there so much broken, buggy crap in the macOS core??? no_r_kelly() { size=$1 last=$((size - 1)) for x in 0 $last; do for y in 0 $last; do echo -n "color $x,$y point " done done } mkdir -p "${outdir}" for size in 32 64 128 256 512 1024; do # Yay Apple thanks for this utter trash OS. last=$((size - 1)) convert -background none -resize "${size}x${size}" "$svg" \ -fill '#ff00' -draw "$(no_r_kelly $size)" \ -strip "png32:${outdir}/icon_${size}x${size}.png" done # Outputs the imagemagick -draw command to color the corner-adjacent pixels as half-transparent # white. We use this for the 16x16 (the others pick up corner transparency from the svg). semitransparent_off_corners() { size=$1 for x in 1 $((size - 2)); do for y in 0 $((size - 1)); do echo -n "color $x,$y point " done done for x in 0 $((size -1)); do for y in 1 $((size - 2)); do echo -n "color $x,$y point " done done } # For 16x16 we crop the image to 5/8 of its regular size before resizing which effectively zooms in # on it a bit because if we resize the full icon it ends up a fuzzy mess, while the crop and resize # lets us retain some detail of the logo. (We don't do this for the 16x16@2x because that is really # 32x32 where it retains enough detail). convert -background none -resize 512x512 "$svg" -gravity Center -extent 320x320 -resize 16x16 \ -fill '#ff00' -draw "$(no_r_kelly 16)" \ -fill '#fff8' -draw "$(semitransparent_off_corners 16)" \ -strip "png32:$outdir/icon_16x16.png" # Create all the "@2x" versions which are just the double-size versions rm -f "${outdir}/icon_*@2x.png" mv "${outdir}/icon_1024x1024.png" "${outdir}/icon_512x512@2x.png" for size in 16 32 128 256; do double=$((size * 2)) ln -f "${outdir}/icon_${double}x${double}.png" "${outdir}/icon_${size}x${size}@2x.png" done iconutil -c icns "${outdir}" ================================================ FILE: contrib/macos/notarize.py.in ================================================ #!/usr/bin/env python3 import sys import plistlib import subprocess import time import os import os.path def bold_red(x): return "\x1b[31;1m" + x + "\x1b[0m" if not @notarize_py_is_sysext@: print(bold_red("\nUnable to notarize: this lokinet is not built as a system extension\n"), file=sys.stderr) sys.exit(1) if not all(("@MACOS_NOTARIZE_USER@", "@MACOS_NOTARIZE_PASS@", "@MACOS_NOTARIZE_ASC@")): print(bold_red("\nUnable to notarize: one or more required notarization variable not set; see contrib/macos/README.txt\n") + " Called with -DMACOS_NOTARIZE_USER=@MACOS_NOTARIZE_USER@\n" " -DMACOS_NOTARIZE_PASS=@MACOS_NOTARIZE_PASS@\n" " -DMACOS_NOTARIZE_ASC=@MACOS_NOTARIZE_ASC@\n", file=sys.stderr) sys.exit(1) os.chdir("@PROJECT_BINARY_DIR@") app = "@lokinet_app@" zipfile = f"Lokinet.app.notarize.zip" print(f"Creating {zipfile} from {app}") if os.path.exists(zipfile): os.remove(zipfile) subprocess.run(['ditto', '-v', '-c', '-k', '--sequesterRsrc', '--keepParent', app, zipfile]) userpass = ('--username', "@MACOS_NOTARIZE_USER@", '--password', "@MACOS_NOTARIZE_PASS@") print("Submitting {} for notarization; this may take a minute...".format(zipfile)) started = time.time() command = [ 'xcrun', 'altool', '--notarize-app', '--primary-bundle-id', 'org.lokinet.@PROJECT_VERSION@', *userpass, '--asc-provider', "@MACOS_NOTARIZE_ASC@", '--file', zipfile, '--output-format', 'xml' ] print(command) result = subprocess.run(command, stdout=subprocess.PIPE) data = plistlib.loads(result.stdout) if 'success-message' not in data or 'notarization-upload' not in data or 'RequestUUID' not in data['notarization-upload']: print("Something failed, leaving you with this nice XML to figure out:\n{}".format(data)) sys.exit(1) uuid = data['notarization-upload']['RequestUUID'] elapsed = time.time() - started mins, secs = int(elapsed // 60), elapsed % 60 print("Notarization submitted with request uuid = {} in {:d}m{:05.2f}s".format(uuid, mins, secs)) print(data['success-message']) print("Begin polling for notarization result") started_waiting = time.time() done = False success = False while not done: time.sleep(5) result = subprocess.run([ 'xcrun', 'altool', '--notarization-info', uuid, *userpass, '--output-format', 'xml' ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode == 1 and b'Gateway Timeout' in result.stderr: status = "Apple's servers are trash (aka Gateway Timeout)" else: result.check_returncode() data = plistlib.loads(result.stdout) if 'notarization-info' not in data or 'Status' not in data['notarization-info']: status = 'Request failed' else: status = data['notarization-info']['Status Message'] if 'Status Message' in data['notarization-info'] else '' st = data['notarization-info']['Status'] if st == 'success': success = True done = True elif st == 'invalid': done = True elif st == 'in progress' and len(status) == 0: status = 'Notarization in progress' if done and 'LogFileURL' in data['notarization-info']: status += '\n\nlog file: {}'.format(data['notarization-info']['LogFileURL']) elapsed = time.time() - started_waiting mins, secs = int(elapsed // 60), int(elapsed % 60) print("\033[1K\r(+{:d}m{:02d}s) {}: {}".format(mins, secs, st, status), end='', flush=True) print("\n") if not success: sys.exit(42) if os.path.exists(zipfile): os.remove(zipfile) print("Stapling {}...".format(app), end='') result = subprocess.run(['xcrun', 'stapler', 'staple', app]) result.check_returncode() with open("macos-notarized.stamp", 'w'): pass print(" success.\n") ================================================ FILE: contrib/macos/seticon.swift ================================================ import Foundation import AppKit // Apple deprecated their command line tools to set images on things and replaced them with a // barely-documented swift function. Yay! // Usage: ./seticon /path/to/my.icns /path/to/some.dmg let args = CommandLine.arguments if args.count != 3 { print("Error: usage: ./seticon /path/to/my.icns /path/to/some.dmg") exit(1) } var icns = args[1] var dmg = args[2] var img = NSImage(byReferencingFile: icns)! if NSWorkspace.shared.setIcon(img, forFile: dmg) { print("Set \(dmg) icon to \(icns) [\(img.size)]") } else { print("Setting icon failed, don't know why") exit(2) } ================================================ FILE: contrib/macos/sign.sh.in ================================================ #!/usr/bin/env bash set -e if [ "@CODESIGN@" != "ON" ]; then echo "Cannot codesign: this build was not configured with codesigning" >&2 exit 1 fi signit() { target="$1" entitlements="$2" echo -e "\n\e[33;1mSigning ${target/*\/Lokinet.app/Lokinet.app}...\e[0m" >&2 codesign \ --verbose=4 \ --force \ -s "@CODESIGN_ID@" \ --entitlements "$entitlements" \ --strict \ --timestamp \ --options=runtime \ "$target" } gui_entitlements="@PROJECT_SOURCE_DIR@/gui/node_modules/app-builder-lib/templates/entitlements.mac.plist" ext_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet-extension.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" app_entitlements="@PROJECT_SOURCE_DIR@/contrib/macos/lokinet.@LOKINET_ENTITLEMENTS_TYPE@.entitlements.plist" SIGN_TARGET="@PROJECT_BINARY_DIR@/Lokinet @PROJECT_VERSION@/Lokinet.app" for ext in systemextension appex; do netext="$SIGN_TARGET/@lokinet_ext_dir@/org.lokinet.network-extension.$ext" if [ -e "$netext" ]; then signit "$netext" "$ext_entitlements" fi done if [ "@LOKINET_GUI@" == "ON" ]; then gui_app="$SIGN_TARGET"/Contents/Helpers/Lokinet-GUI.app gui_sign_targets=() for bundle in \ "$gui_app"/Contents/Frameworks/*.framework \ "$gui_app"/Contents/Frameworks/*.app do if [ -d "$bundle/Libraries" ]; then gui_sign_targets+=("$bundle"/Libraries/*.dylib) fi if [ -d "$bundle/Helpers" ]; then gui_sign_targets+=("$bundle"/Helpers/*) fi if [ -d "$bundle/Resources" ]; then for f in "$bundle/Resources"/*; do if [[ -f "$f" && -x "$f" && "$(file -b "$f")" == Mach-O* ]]; then gui_sign_targets+=("$f") fi done fi gui_sign_targets+=("$bundle") done gui_sign_targets+=("$gui_app") for target in "${gui_sign_targets[@]}"; do signit "$target" "$gui_entitlements" done signit "$SIGN_TARGET"/Contents/MacOS/Lokinet "$app_entitlements" fi signit "$SIGN_TARGET" "$app_entitlements" touch "@PROJECT_BINARY_DIR@"/macos-signed.stamp ================================================ FILE: contrib/make-ico.sh ================================================ #!/bin/bash # Invoked from cmake as make-ico.sh /path/to/icon.svg /path/to/output.ico svg="$1" out="$2" outdir="$out.d" set -e sizes=(16 24 32 40 48 64 96 192 256) outs="" mkdir -p "${outdir}" for size in "${sizes[@]}"; do outf="${outdir}/${size}x${size}.png" if [ $size -lt 32 ]; then # For 16x16 and 24x24 we crop the image to 2/3 of its regular size make it all white # (instead of transparent) to zoom in on it a bit because if we resize the full icon to the # target size it ends up a fuzzy mess, while the crop and resize lets us retain some detail # of the logo. rsvg-convert -b white \ --page-height $size --page-width $size \ -w $(($size*3/2)) -h $(($size*3/2)) --left " -$(($size/4))" --top " -$(($size/4))" \ "$svg" >"$outf" else rsvg-convert -b transparent -w $size -h $size "$svg" >"$outf" fi outs="-r $outf $outs" done icotool -c -b 32 -o "$out" $outs ================================================ FILE: contrib/omq-rpc.py ================================================ #!/usr/bin/env python3 import nacl.bindings as sodium from nacl.public import PrivateKey from nacl.signing import SigningKey, VerifyKey import nacl.encoding import requests import zmq import zmq.utils.z85 import sys import re import time import random import shutil context = zmq.Context() socket = context.socket(zmq.DEALER) socket.setsockopt(zmq.CONNECT_TIMEOUT, 5000) socket.setsockopt(zmq.HANDSHAKE_IVL, 5000) #socket.setsockopt(zmq.IMMEDIATE, 1) if len(sys.argv) > 1 and any(sys.argv[1].startswith(x) for x in ("ipc://", "tcp://", "curve://")): remote = sys.argv[1] del sys.argv[1] else: remote = "ipc://./rpc.sock" curve_pubkey = b'' my_privkey, my_pubkey = b'', b'' # If given a curve://whatever/pubkey argument then transform it into 'tcp://whatever' and put the # 'pubkey' back into argv to be handled below. if remote.startswith("curve://"): pos = remote.rfind('/') pkhex = remote[pos+1:] remote = "tcp://" + remote[8:pos] if len(pkhex) != 64 or not all(x in "0123456789abcdefABCDEF" for x in pkhex): print("curve:// addresses must be in the form curve://HOST:PORT/REMOTE_PUBKEY_HEX", file=sys.stderr) sys.exit(1) sys.argv[1:0] = [pkhex] if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): curve_pubkey = bytes.fromhex(sys.argv[1]) del sys.argv[1] socket.curve_serverkey = curve_pubkey if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): my_privkey = bytes.fromhex(sys.argv[1]) del sys.argv[1] my_pubkey = zmq.utils.z85.decode(zmq.curve_public(zmq.utils.z85.encode(my_privkey))) else: my_privkey = PrivateKey.generate() my_pubkey = my_privkey.public_key.encode() my_privkey = my_privkey.encode() print("No curve client privkey given; generated a random one (pubkey: {}, privkey: {})".format( my_pubkey.hex(), my_privkey.hex()), file=sys.stderr) socket.curve_secretkey = my_privkey socket.curve_publickey = my_pubkey if not 2 <= len(sys.argv) <= 3 or any(x in y for x in ("--help", "-h") for y in sys.argv[1:]): print("Usage: {} [ipc:///path/to/sock|tcp://1.2.3.4:5678] [SERVER_CURVE_PUBKEY [LOCAL_CURVE_PRIVKEY]] COMMAND ['JSON']".format( sys.argv[0]), file=sys.stderr) sys.exit(1) beginning_of_time = time.clock_gettime(time.CLOCK_MONOTONIC) print("Connecting to {}".format(remote), file=sys.stderr) socket.connect(remote) to_send = [sys.argv[1].encode(), b'tagxyz123'] to_send += (x.encode() for x in sys.argv[2:]) print("Sending {}".format(to_send[0]), file=sys.stderr) socket.send_multipart(to_send) if socket.poll(timeout=5000): m = socket.recv_multipart() recv_time = time.clock_gettime(time.CLOCK_MONOTONIC) if len(m) < 3 or m[0:2] != [b'REPLY', b'tagxyz123']: print("Received unexpected {}-part reply:".format(len(m)), file=sys.stderr) for x in m: print("- {}".format(x)) else: # m[2] is numeric value, m[3] is data part, and will become m[2] <- changed print("Received reply in {:.6f}s:".format(recv_time - beginning_of_time), file=sys.stderr) if len(m) < 3: print("(empty reply data)", file=sys.stderr) else: for x in m[2:]: print("{} bytes data part:".format(len(x)), file=sys.stderr) if any(x.startswith(y) for y in (b'd', b'l', b'i')) and x.endswith(b'e'): sys.stdout.buffer.write(x) else: print(x.decode(), end="\n\n") else: print("Request timed out", file=sys.stderr) socket.close(linger=0) sys.exit(1) # sample usage: # ./omq-rpc.py ipc://$HOME/.oxen/testnet/oxend.sock 'llarp.get_service_nodes' | jq ================================================ FILE: contrib/patches/libzmq-mingw-unistd.patch ================================================ diff --git a/tests/testutil.hpp b/tests/testutil.hpp index c6f5e4de78..09b9fa77e5 100644 --- a/tests/testutil.hpp +++ b/tests/testutil.hpp @@ -41,6 +41,9 @@ // For AF_INET and IPPROTO_TCP #if defined _WIN32 #include "../src/windows.hpp" +#if defined(__MINGW32__) +#include +#endif #else #include #include ================================================ FILE: contrib/patches/libzmq-mingw-wepoll.patch ================================================ diff --git a/external/wepoll/wepoll.c b/external/wepoll/wepoll.c --- a/external/wepoll/wepoll.c +++ b/external/wepoll/wepoll.c @@ -140,9 +140,9 @@ #pragma warning(push, 1) #endif -#include -#include -#include +#include +#include +#include #ifndef __GNUC__ #pragma warning(pop) ================================================ FILE: contrib/patches/unbound-delete-crash-fix.patch ================================================ commit 56d816014d5e8a7eb055169c7e13a303dad5e50f Author: Jason Rhinelander Date: Mon Oct 31 22:07:03 2022 -0300 Set tube->ev_listen to NULL to prevent double unregister On windows when using threaded mode (i.e. `ub_ctx_async(ctx, 1)`) tube_remove_bg_listen gets called twice: once when the thread does its own cleanup, then again in `tube_delete()`. Because `ev_listen` doesn't get cleared, however, we end we calling ub_winsock_unregister_wsaevent with a freed pointer. This doesn't always manifest because, apparently, for various compilers and settings that memory *might* be overwritten in which case the additional check for ev->magic will prevent anything actually happening, but in my case under mingw32 that doesn't happen and we end up eventually crashing. This fixes the crash by properly NULLing the pointer so that the second ub_winsock_unregister_wsaevent(...) becomes a no-op. diff --git a/util/tube.c b/util/tube.c index 43455fee..a92dfa77 100644 --- a/util/tube.c +++ b/util/tube.c @@ -570,6 +570,7 @@ void tube_remove_bg_listen(struct tube* tube) { verbose(VERB_ALGO, "tube remove_bg_listen"); ub_winsock_unregister_wsaevent(tube->ev_listen); + tube->ev_listen = NULL; } void tube_remove_bg_write(struct tube* tube) ================================================ FILE: contrib/py/.gitignore ================================================ *.egg-info v __pycache__ dist ================================================ FILE: contrib/py/admin/.gitignore ================================================ v/ ================================================ FILE: contrib/py/admin/lokinetmon ================================================ #!/usr/bin/env python3 import curses import json import sys import time import platform import os import re from argparse import ArgumentParser as AP is_windows = lambda : platform.system().lower() == 'windows' is_linux = lambda : platform.system().lower() == 'linux' try: import zmq except ImportError: print("zmq module not found") print() if is_linux(): print("for debian-based linux do:") print("\tsudo apt install python3-zmq") print("for other linuxs do:") print("\tpip3 install --user zmq") else: print("install it with:") print("\tpip3 install --user zmq") sys.quit() geo = None try: import GeoIP except ImportError: print("geoip module not found") print() if is_linux(): print("for debian-based linux do:") print("\tsudo apt install python3-geoip") print("for other linuxs do:") print("\tpip3 install --user geoip") print("for other linuxs you are responsible for obtaining your owen geoip databases, glhf") time.sleep(1) else: print("install it with:") print("\tpip3 install --user geoip") print() print("press enter to continue without geoip") sys.stdin.read(1) else: try: geoip_env_var = 'GEOIP_DB_FILE' if is_windows(): geoip_default_db = '.\\GeoIP.dat' else: geoip_default_db = "/usr/share/GeoIP/GeoIP.dat" geoip_db_file = geoip_env_var in os.environ and os.environ[geoip_env_var] or geoip_default_db if not os.path.exists(geoip_db_file): print("no geoip database found at {}".format(geoip_db_file)) print("you can override the path to it using the {} environmental variable".format(geoip_env_var)) sys.quit() geo = GeoIP.open(geoip_db_file, GeoIP.GEOIP_STANDARD) except Exception as ex: print('failed to load geoip database: {}'.format(ex)) time.sleep(1) now = lambda : int(time.time()) * 1000 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: _sample_size = 12 filter = lambda x : True def __init__(self, url, introsetMode=False): self.txrate = 0 self.rxrate = 0 self.data = dict() self.win = curses.initscr() curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) self._rpc_context = zmq.Context() self._rpc_socket = self._rpc_context.socket(zmq.DEALER) self._rpc_socket.setsockopt(zmq.CONNECT_TIMEOUT, 5000) self._rpc_socket.setsockopt(zmq.HANDSHAKE_IVL, 5000) self._rpc_socket.connect(url) self._speed_samples = [(0,0,0,0)] * self._sample_size self._run = True self._introsetMode = introsetMode def rpc(self, method): self._rpc_socket.send_multipart([method.encode(), b'lokinetmon'+method.encode()]) if not self._rpc_socket.poll(timeout=50): return 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 curses.endwin() def update_data(self): """update data from lokinet""" try: data = json.loads(self.rpc("llarp.status")) self.data = data['result'] except: self.data = None return self.data is not None and self._run def _render_path(self, y_pos, path, name): """render a path at current position""" self.win.move(y_pos, 1) self.win.addstr("({}) ".format(name)) y_pos += 1 self.win.move(y_pos, 1) y_pos += 1 self.win.addstr("[tx:\t{}]\t[rx:\t{}]".format( self.speed_of(path['txRateCurrent']), self.speed_of(path['rxRateCurrent']))) self.win.move(y_pos, 1) y_pos += 1 self.win.addstr("me -> ") for hop in path["hops"]: 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(" [expires: {}]".format(self.time_to(path["expiresAt"]))) if path["expiresSoon"]: self.win.addstr("(expiring)") elif path["expired"]: self.win.addstr("(expired)") return y_pos @staticmethod def time_to(timestamp): """ return time until timestamp formatted""" if timestamp: unit = 'seconds' val = (timestamp - now()) / 1000.0 if abs(val) > 60.0: val /= 60.0 unit = 'minutes' if val < 0: return "{:.2f} {} ago".format(0-val, unit) else: return "in {:.2f} {}".format(val, unit) else: return 'never' @staticmethod def speed_of(rate): """turn int speed into string formatted""" units = ["b", "Kb", "Mb", "Gb"] idx = 0 rate *= 8 while rate > 1000 and idx < len(units): rate /= 1000.0 idx += 1 return "{} {}ps".format("%.2f" % rate, units[idx]) def get_all_paths(self): """ yield all paths in current data """ for key in self.data['services']: status = self.data['services'][key] for path in (status['paths'] or []): yield path for sess in (status['remoteSessions'] or []): for path in sess['paths']: yield path for sess in (status['snodeSessions'] or []): for path in sess['paths']: yield path def display_service(self, y_pos, name, status): """display a service at current position""" self.win.move(y_pos, 1) self.win.addstr("service [{}]".format(name)) build = status["buildStats"] ratio = build["success"] / (build["attempts"] or 1) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("build success: {} %".format(int(100 * ratio))) y_pos += 1 self.win.move(y_pos, 1) paths = status["paths"] self.win.addstr("paths: {}".format(len(paths))) for path in paths: if self.filter('localhost.loki'): y_pos = self._render_path(y_pos, path, "localhost.loki") for session in (status["remoteSessions"] or []): for path in session["paths"]: if self.filter(session["remoteIdentity"]): y_pos = self._render_path( y_pos, path, "[active] {}".format(session["currentConvoTag"]) ) for session in (status["snodeSessions"] or []): for path in session["paths"]: if self.filter(session["endpoint"]): y_pos = self._render_path(y_pos, path, "[snode]") return y_pos def display_links(self, y_pos, data): """ display links section """ self.txrate = 0 self.rxrate = 0 for link in data["outbound"]: y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("outbound sessions:") y_pos = self.display_link(y_pos, link) for link in data["inbound"]: y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("inbound sessions:") y_pos = self.display_link(y_pos, link) y_pos += 2 self.win.move(y_pos, 1) self.win.addstr( "throughput:\t\t[{}\ttx]\t[{}\trx]".format( self.speed_of(self.txrate), self.speed_of(self.rxrate) ) ) bloat_tx, bloat_rx = self.calculate_bloat(self.data['links']['outbound']) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("goodput:\t\t[{}\ttx]\t[{}\trx]".format( self.speed_of(self.txrate-bloat_tx), self.speed_of(self.rxrate-bloat_rx))) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("overhead:\t\t[{}\ttx]\t[{}\trx]".format( self.speed_of(bloat_tx), self.speed_of(bloat_rx))) self._speed_samples.append((self.txrate, self.rxrate, bloat_tx, bloat_rx)) while len(self._speed_samples) > self._sample_size: self._speed_samples.pop(0) return self.display_speedgraph(y_pos + 2) @staticmethod def _scale(_x, _n): while _n > 0: _x /= 2 _n -= 1 return int(_x) @staticmethod def _makebar(samp, badsamp, maxsamp): 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 for _tx, _rx, b_tx, b_rx in self._speed_samples: if _tx > txmax: txmax = _tx if _rx > rxmax: rxmax = _rx rxscale = 0 while rxmax > maxsz: rxscale += 1 rxmax /= 2 txscale = 0 while txmax > maxsz: txscale += 1 txmax /= 2 txlabelpad = int(txmax / 2) rxlabelpad = int(rxmax / 2) if txlabelpad <= 0: txlabelpad = 1 if rxlabelpad <= 0: rxlabelpad = 1 txlabelpad_str = " " * txlabelpad rxlabelpad_str = " " * rxlabelpad y_pos += 1 self.win.move(y_pos, 1) for val in [txlabelpad_str, 'tx', txlabelpad_str, rxlabelpad_str, 'rx', rxlabelpad_str]: self.win.addstr(val) for _tx, _rx, b_tx, b_rx in self._speed_samples: y_pos += 1 self.win.move(y_pos, 1) txpad, txbar, btxbar = self._makebar(self._scale(_tx, txscale), self._scale(b_tx, txscale), int(txmax)) rxpad, rxbar, brxbar = self._makebar(self._scale(_rx, rxscale), self._scale(b_rx, rxscale), int(rxmax)) self.win.addstr(txpad) self.win.addstr(btxbar, curses.color_pair(1)) self.win.addstr(txbar) self.win.addstr('|') self.win.addstr(rxbar) self.win.addstr(brxbar, curses.color_pair(1)) self.win.addstr(rxpad) return y_pos + 2 def calculate_bloat(self, links): """ calculate bandwith overhead """ paths = self.get_all_paths() lltx = 0 llrx = 0 _tx = 0 _rx = 0 for link in links: sessions = link["sessions"]["established"] for sess in sessions: lltx += sess['tx'] llrx += sess['rx'] for path in paths: _tx += path['txRateCurrent'] _rx += path['rxRateCurrent'] lltx -= _tx llrx -= _rx if lltx < 0: lltx = 0 if llrx < 0: llrx = 0 return lltx, llrx def display_link(self, y_pos, link): """ display links """ y_pos += 1 self.win.move(y_pos, 1) sessions = link["sessions"]["established"] or [] for sess in sessions: y_pos = self.display_link_session(y_pos, sess) return y_pos def display_link_session(self, y_pos, sess): """ display link sessions """ y_pos += 1 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( addr, self.speed_of(sess["txRateCurrent"]), self.speed_of(sess["rxRateCurrent"]) ) ) if (sess['txMsgQueueSize'] or 0) > 1: self.win.addstr(" [out window: {}]".format(sess['txMsgQueueSize'])) if (sess['rxMsgQueueSize'] or 0) > 1: self.win.addstr(" [in window: {}]".format(sess['rxMsgQueueSize'])) def display(acks, label, num='acks', dem='packets'): if acks[dem] > 0: self.win.addstr(" [{}: {}]".format(label, round(float(acks[num]) / float(acks[dem]), 2))) if ('recvMACKs' in sess) and ('sendMACKs' in sess): display(sess['sendMACKs'], 'out MACK density') display(sess['recvMACKs'], 'in MACK density') dats = {'recvAcks': 'in acks', 'sendAcks': 'out acks', 'recvRTX': 'in RTX', 'sendRTX': 'out RTX'} for key in dats: val = dats[key] if (key in sess) and (sess[key] > 0): self.win.addstr(" [{}: {}]".format(val, sess[key])) return y_pos def display_dht(self, y_pos, data): """ display dht window """ y_pos += 2 self.win.move(y_pos, 1) self.win.addstr("DHT:") y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("introset lookups") y_pos = self.display_bucket(y_pos, data["pendingIntrosetLookups"]) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("router lookups") return self.display_bucket(y_pos, data["pendingRouterLookups"]) def display_bucket(self, y_pos, data): """ display dht bucket """ txs = data["tx"] self.win.addstr(" ({} lookups)".format(len(txs))) for transaction in txs: y_pos += 1 self.win.move(y_pos, 1) self.win.addstr("search for {}".format(transaction["tx"]["target"])) return y_pos def display_introsets(self, y_pos, service): """ display introsets on a service """ y_pos += 1 self.win.move(y_pos, 1) if self.filter("localhost.loki"): self.win.addstr("localhost.loki") y_pos = self._display_our_introset(y_pos, service) y_pos += 1 remotes = service['remoteSessions'] or [] for session in remotes: if self.filter(session['remoteIdentity']): y_pos = self._display_session_introset(y_pos, session) def _display_intro(self, y_pos, intro, label, paths): y_pos += 1 self.win.move(y_pos, 1) path = 'path' in intro and intro['path'][:4] or '????' self.win.addstr('{}: ({}|{}) [expires: {}] [{} paths]'.format(label, intro['router'][:8], path, self.time_to(intro['expiresAt']), self.count_endpoints_in_path(paths, intro['router']))) return y_pos @staticmethod def count_endpoints_in_path(paths, endpoint): num = 0 for path in paths: if path['hops'][-1]['router'] == endpoint and path['ready']: num += 1 return num @staticmethod def count_ready_paths(paths): num = 0 for path in paths: if path['ready']: num += 1 return num @staticmethod def make_bar(timestamp, scale=1, char='#'): if timestamp > 0: return int((abs(timestamp - now()) / 1000) / scale) * char return '' def _display_our_introset(self, y_pos, context): for intro in context['introset']['intros'] or []: y_pos = self._display_intro(y_pos, intro, "introset intro", context['paths']) for path in context['paths']: y_pos = self._display_intro(y_pos, path['intro'], "inbound path [created {}]".format(self.time_to(path['buildStarted'])), context['paths']) return y_pos def _display_session_introset(self, y_pos, context): #print(context.keys()) y_pos += 1 self.win.move(y_pos, 1) readyState = context['readyToSend'] and '️✅' or '❌' self.win.addstr('{} ({}) [{}]'.format(context['remoteIdentity'], context['currentConvoTag'], readyState)) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr('created: {}'.format(self.time_to(context['sessionCreatedAt']))) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr('last good send: {}'.format(self.time_to(context['lastGoodSend']))) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr(self.make_bar(context['lastGoodSend'])) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr('last recv: {}'.format(self.time_to(context['lastRecv']))) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr(self.make_bar(context['lastRecv'])) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr('last introset update: {}'.format(self.time_to(context['lastIntrosetUpdate']))) y_pos += 1 self.win.move(y_pos, 1) self.win.addstr(self.make_bar(context['lastIntrosetUpdate'], 30)) y_pos += 2 self.win.move(y_pos, 1) self.win.addstr('last shift: {}'.format(self.time_to(context['lastShift']))) paths = context['paths'] or [] y_pos = self._display_intro(y_pos + 1, context['nextIntro'], 'next intro', paths) + 1 y_pos = self._display_intro(y_pos, context['remoteIntro'], 'current intro', paths) + 1 for intro in context['currentRemoteIntroset']['intros'] or []: y_pos = self._display_intro(y_pos, intro, "introset intro", paths) y_pos += 1 return y_pos def display_data(self): """ draw main window """ if self.data is not None: if self.version: self.win.addstr(1, 1, self.version) services = self.data["services"] or {} y_pos = 3 try: if not self._introsetMode: y_pos = self.display_links(y_pos, self.data["links"]) for key in services: y_pos = self.display_service(y_pos, key, services[key]) y_pos = self.display_dht(y_pos, self.data["dht"]) else: for key in services: y_pos = self.display_introsets(y_pos, services[key]) except Exception as ex: print('{}'.format(ex)) else: self.win.move(1, 1) self.win.addstr("lokinet offline") def run(self): """ run mainloop """ try: self.version = json.loads(self.rpc("llarp.version"))['result']['version'] except: self.version = None while self._run: if self.update_data(): self.win.box() self.display_data() elif self._run: self.win.addstr(1, 1, "offline") else: self._close() return self.win.refresh() try: time.sleep(1) except: self._close() return self.win.clear() def main(): """ main function """ ap = AP() ap.add_argument("--introset", action='store_const', const=True, default=False, help="run in introset inspection mode") ap.add_argument("--url", default='tcp://127.0.0.1:1190', type=str, help='url to lokinet rpc') ap.add_argument('--filter', default='.+', type=str, help="regex to filter entries") ap.add_argument('--invert-filter', const=True, default=False, action='store_const', help='invert regex filter matching') args = ap.parse_args() mon = Monitor( args.url, args.introset ) mon.filter = lambda x : re.match(args.filter, x) is not None if args.invert_filter: old_filter = mon.filter mon.filter = lambda x : not old_filter(x) mon.run() if __name__ == "__main__": main() ================================================ FILE: contrib/py/admin/requirements.txt ================================================ requests ================================================ FILE: contrib/py/ffi-example/lokinet.py ================================================ #!/usr/bin/env python3 from ctypes import * import signal import time import threading import os lib_file = os.path.join(os.path.realpath('.'), 'liblokinet.so') class LokiNET(threading.Thread): lib = None ctx = None def load(self, lib, conf): self.lib = CDLL(lib) self.lib.llarp_ensure_config(conf) self.ctx = self.lib.llarp_main_init(conf) return self.ctx != 0 def inform_fail(self): """ inform lokinet crashed """ def inform_end(self): """ inform lokinet ended clean """ def signal(self, sig): if self.ctx and self.lib: self.lib.llarp_main_signal(self.ctx, int(sig)) def run(self): code = self.lib.llarp_main_run(self.ctx) print("llarp_main_run exited with status {}".format(code)) if code: self.inform_fail() else: self.inform_end() def close(self): if self.lib and self.ctx: self.lib.llarp_main_free(self.ctx) def main(): loki = LokiNET() if loki.load(lib_file, b'daemon.ini'): if loki.configure(): loki.start() else: print("failed to configure lokinet context") try: while True: time.sleep(1) except KeyboardInterrupt: llarp.signal(signal.SIGINT) finally: loki.close() return if __name__ == '__main__': main() ================================================ FILE: contrib/py/keygen/.gitignore ================================================ *.private ================================================ FILE: contrib/py/keygen/keygen.py ================================================ #!/usr/bin/env python3 """ keygen tool for lokinet """ from argparse import ArgumentParser as AP from base64 import b32encode from nacl.signing import SigningKey def base32z(data): """ base32 z encode """ return b32encode(data).translate( bytes.maketrans( b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', b'ybndrfg8ejkmcpqxot1uwisza345h769')).decode().rstrip('=') def main(): """ main function for keygen """ argparser = AP() argparser.add_argument('--keyfile', type=str, required=True, help='place to put generated keys') args = argparser.parse_args() secret = SigningKey.generate() with open(args.keyfile, 'wb') as wfile: wfile.write(b'd1:s64:') wfile.write(secret.encode()) wfile.write(secret.verify_key.encode()) wfile.write(b'e') print("{}.loki".format(base32z(secret.verify_key.encode()))) if __name__ == '__main__': main() ================================================ FILE: contrib/py/keygen/readme.md ================================================ # lokinet key generator requires: * python3.7 or higher * pynacl usage: ```bash ./keygen.py --keyfile somekeyfile.private ``` this will overwrite the keyfile with new keys ================================================ FILE: contrib/py/lnproxy/lnproxy/__main__.py ================================================ #!/usr/bin/env python3 from http.server import ThreadingHTTPServer as Server from http.server import BaseHTTPRequestHandler as BaseHandler bootstrapFromURL = True try: import requests except ImportError: bootstrapFromURL = False import ctypes from ctypes.util import find_library import selectors import socket import os class ResultStruct(ctypes.Structure): _pack_ = 1 _fields_ = [ ("err", ctypes.c_int), ("local_address", ctypes.c_char * 256), ("local_port", ctypes.c_int), ("stream_id", ctypes.c_int) ] def __repr__(self): return "".format(self.err, self.local_address, self.local_port, self.stream_id) class LNContext(ctypes.Structure): pass class Context: """ wrapper around liblokinet """ def __init__(self, debug=False): self._ln = ctypes.CDLL(find_library("lokinet")) self._c = ctypes.CDLL(find_library("c")) self._ln.lokinet_context_new.restype = ctypes.POINTER(LNContext) self._ln.lokinet_address.restype = ctypes.c_char_p self._ln.lokinet_address.argtypes = (ctypes.POINTER(LNContext), ) self._ln.lokinet_outbound_stream.restype = ctypes.POINTER(ResultStruct) self._ln.lokinet_outbound_stream.argtypes = (ctypes.POINTER(ResultStruct), ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(LNContext)) self._ctx = self._ln.lokinet_context_new() self._addrmap = dict() self._debug = debug lvl = 'none' if self._debug: lvl = 'debug' self._ln.lokinet_log_level(ctypes.create_string_buffer(lvl.encode('ascii'))) def free(self, ptr): self._c.free(ptr) def add_bootstrap(self, data): ptr = ctypes.create_string_buffer(data) ptrlen = ctypes.c_size_t(len(data)) return self.ln_call("lokinet_add_bootstrap_rc", ptr, ptrlen) def wait_for_ready(self, ms): return self.ln_call("lokinet_wait_for_ready", ms) == 0 def ready(self): return self.ln_call("lokinet_status") == 0 def addr(self): return self._ln.lokinet_address(self._ctx).decode('ascii') def expose(self, port): return self.ln_call('lokinet_inbound_stream', port) def ln_call(self, funcname, *args): args += (self._ctx,) if self._debug: print("call {}{}".format(funcname, args)) return self._ln[funcname](*args) def expose(self, port): port = int(port) print("exposing loopback port: {}".format(port)) return self.ln_call("lokinet_inbound_stream", port) def start(self): return self.ln_call("lokinet_context_start") def stop(self): self.ln_call("lokinet_context_stop") def hasAddr(self, addr): return addr in self._addrmap def putAddr(self, addr, val): self._addrmap[addr] = val def getAddr(self, addr): if addr in self._addrmap: return self._addrmap[addr] def delAddr(self, addr): if addr in self._addrmap: del self._addrmap[addr] def __del__(self): self.stop() self._ln_call("lokinet_context_free") def set_netid(self, netid): self._ln.lokinet_set_netid(ctypes.create_string_buffer(netid.encode('ascii'))) class Stream: def __init__(self, ctx): self._ctx = ctx self._id = None def connect(self, remote): result = ResultStruct() self._ctx.ln_call("lokinet_outbound_stream", ctypes.cast(ctypes.addressof(result), ctypes.POINTER(ResultStruct)), ctypes.create_string_buffer(remote.encode()), ctypes.c_char_p(0)) if result.err: print(result.err) return addr = result.local_address.decode('ascii') port = result.local_port self._id = result.stream_id print("connection to {} made via {}:{} via {}".format(remote, addr, port, self._id)) return addr, port def close(self): if self._id is not None: self._ctx.ln_call("lokinet_close_stream", self._id) def read_and_forward_or_close(readfd, writefd): read = 0 while True: data = os.read(readfd, 1024) read += len(data) if data and len(data) > 0: writefd.write(data) writefd.flush() return True else: return read > 0 ctx = None class Handler(BaseHandler): def do_CONNECT(self): self.connect(self.path) def connect(self, host): global ctx if not ctx.ready(): self.send_error(501) return if not ctx.hasAddr(host): stream = Stream(ctx) result = None try: result = stream.connect(host) except: pass if not result: self.send_error(503) return ctx.putAddr(host, result) sock = socket.socket() try: sock.connect(ctx.getAddr(host)) except: ctx.delAddr(host) self.send_error(504) return self.send_response_only(200) self.end_headers() sel = selectors.DefaultSelector() sock.setblocking(False) sockfd = sock.makefile('rwb') sel.register(self.rfile.fileno(), selectors.EVENT_READ, lambda x : read_and_forward_or_close(x, sockfd)) sel.register(sock.fileno(), selectors.EVENT_READ, lambda x : read_and_forward_or_close(x, self.wfile)) while True: events = sel.select(1) if not events: continue for key, mask in events: if not key.data(key.fileobj): sel.unregister(self.rfile) sel.unregister(sock) sel.close() return import os import sys from argparse import ArgumentParser as AP ap = AP() ap.add_argument("--ip", type=str, help="ip to bind to", default="127.0.0.1") ap.add_argument("--port", type=int, help="port to bind to", default=3000) ap.add_argument("--expose", type=int, help="expose a port to loopback") ap.add_argument("--bootstrap", type=str, help="bootstrap file", default="bootstrap.signed") ap.add_argument("--netid", type=str, help="override network id") ap.add_argument("--debug", action="store_const", const=True, default=False, help="enable verose logging") if bootstrapFromURL: ap.add_argument("--bootstrap-url", type=str, help="bootstrap from remote url", default="https://seed.lokinet.org/lokinet.signed") args = ap.parse_args() addr = (args.ip, args.port) server = Server(addr, Handler) ctx = Context(args.debug) if args.netid is not None: print("overriding netid: {}".format(args.netid)) ctx.set_netid(args.netid) if os.path.exists(args.bootstrap): with open(args.bootstrap, 'rb') as f: if ctx.add_bootstrap(f.read()) == 0: print("loaded {}".format(args.bootstrap)) if args.bootstrap_url is not None: print('getting bootstrap info from {}'.format(args.bootstrap_url)) resp = requests.get(args.bootstrap_url) if resp.status_code == 200 and ctx.add_bootstrap(resp.content) == 0: pass else: print("failed") print("starting up...") if ctx.start() != 0: print("failed to start") ctx.stop() sys.exit(-1) id = None try: while not ctx.wait_for_ready(500): print("waiting for lokinet...") lokiaddr = ctx.addr() print("we are {}".format(lokiaddr)) if args.expose: id = ctx.expose(args.expose) print("exposed {}:{}".format(lokiaddr, args.expose)) print("serving on {}:{}".format(*addr)) server.serve_forever() finally: if id is not None: ctx.ln_call("lokinet_close_stream", id) ctx.stop() ================================================ FILE: contrib/py/lnproxy/readme.md ================================================ # LN Proxy embedded lokinet that provides an http tunnel proxy (using http connect) if `python3-requests` is installed then we can bootstrap from url using `--bootstrap-url` flag usage: $ python3 -m lnproxy [--ip ip | --port port] ================================================ FILE: contrib/py/quic_tester.py ================================================ #!/usr/bin/env python3 import nacl.bindings as sodium from nacl.public import PrivateKey from nacl.signing import SigningKey, VerifyKey import nacl.encoding import requests import zmq import zmq.utils.z85 import sys import os import re import time import random import shutil import json context = zmq.Context() socket = context.socket(zmq.DEALER) socket.setsockopt(zmq.CONNECT_TIMEOUT, 5000) socket.setsockopt(zmq.HANDSHAKE_IVL, 5000) #socket.setsockopt(zmq.IMMEDIATE, 1) if len(sys.argv) > 1 and any(sys.argv[1].startswith(x) for x in ("ipc://", "tcp://")): remote = sys.argv[1] del sys.argv[1] else: remote = "ipc://./loki.sock" curve_pubkey = b'' my_privkey, my_pubkey = b'', b'' if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): curve_pubkey = bytes.fromhex(sys.argv[1]) del sys.argv[1] socket.curve_serverkey = curve_pubkey if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): my_privkey = bytes.fromhex(sys.argv[1]) del sys.argv[1] my_pubkey = zmq.utils.z85.decode(zmq.curve_public(zmq.utils.z85.encode(my_privkey))) else: my_privkey = PrivateKey.generate() my_pubkey = my_privkey.public_key.encode() my_privkey = my_privkey.encode() print("No curve client privkey given; generated a random one (pubkey: {}, privkey: {})".format( my_pubkey.hex(), my_privkey.hex()), file=sys.stderr) socket.curve_secretkey = my_privkey socket.curve_publickey = my_pubkey if not 2 <= len(sys.argv) or any(x in y for x in ("--help", "-h") for y in sys.argv[1:]): print("Usage: {} [ipc:///path/to/sock|tcp://1.2.3.4:5678] [connect|listen] host port".format( sys.argv[0]), file=sys.stderr) sys.exit(1) action = sys.argv[1].lower() host = sys.argv[2] port = int(sys.argv[3]) request_path = len(sys.argv) >= 5 and sys.argv[4] or '/' beginning_of_time = time.clock_gettime(time.CLOCK_MONOTONIC) #print("Connecting to {}".format(remote), file=sys.stderr) socket.connect(remote) def rpc(method, args, timeout=15000): to_send = [method.encode(), b'tagxyz123'] to_send += (x.encode() for x in [json.dumps(args)]) #print("Sending {}".format(to_send[0]), file=sys.stderr) socket.send_multipart(to_send) if socket.poll(timeout=timeout): m = socket.recv_multipart() recv_time = time.clock_gettime(time.CLOCK_MONOTONIC) if len(m) < 3 or m[0:2] != [b'REPLY', b'tagxyz123']: pass else: return json.loads(m[2].decode()) args = {"host":host, "port":port} def success_or_die(response): if response: if 'error' in response and response['error']: print("error: {}".format(response['error'])) socket.close(linger=0) sys.exit(1) if response and 'result' in response: return response["result"] else: print("no response") socket.close(linger=0) sys.exit(1) if action == "connect": result = success_or_die(rpc("llarp.quic_connect", args)) print(result) cmd = "curl -vv http://{}{} -o /dev/null".format(result["addr"], request_path) print("{}".format(cmd)) os.system(cmd) if action == "listen": result = success_or_die(rpc("llarp.quic_listener", args)) print("ID={} addr={}".format(result["id"], result["addr"])) ================================================ FILE: contrib/readme-installer.txt ================================================ Lokinet is the reference implementation of LLARP (low latency anonymous routing protocol), a layer 3 onion routing protocol. This installer provides the needed control panel to get up an running on Lokinet. You can view additional documentation and information on Lokinet at https://lokinet.org ================================================ FILE: contrib/systemd-resolved/README.md ================================================ 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 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 ``` ================================================ FILE: contrib/systemd-resolved/lokinet.pkla ================================================ [Allow lokinet to set DNS settings] Identity=unix-user:_lokinet Action=org.freedesktop.resolve1.set-dns-servers;org.freedesktop.resolve1.set-domains ResultAny=yes ================================================ FILE: contrib/systemd-resolved/lokinet.rules ================================================ /* 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; } }); ================================================ FILE: contrib/tarball.sh ================================================ #!/usr/bin/env bash # # create signed release tarball with submodules bundled # usage: ./contrib/tarball.sh [keyid] # repo=$(readlink -e $(dirname $0)/..) branch=$(test -e $repo/.git/ && git rev-parse --abbrev-ref HEAD) out="lokinet-$(git describe --exact-match --tags $(git log -n1 --pretty='%h') 2> /dev/null || ( echo -n $branch- && git rev-parse --short HEAD)).tar.xz" git-archive-all -C $repo --force-submodules $out && rm -f $out.sig && (gpg -u ${1:-jeff@lokinet.io} --sign --detach $out &> /dev/null && gpg --verify $out.sig) ================================================ FILE: contrib/windows-configure.sh ================================================ #!/bin/bash set -e set -x # Usage: windows-configure.sh [rootdir [builddir]] -DWHATEVER=BLAH ... if [ $# -ge 1 ] && [[ "$1" != -* ]]; then root="$1" shift else root="$(dirname $0)"/.. fi root="$(readlink -f "$root")" if [ $# -ge 1 ] && [[ "$1" != -* ]]; then build="$(readlink -f "$1")" shift else build="$root/build/win32" echo "Setting up build in $build" fi mkdir -p "$build" cmake \ -S "$root" -B "$build" \ -G 'Unix Makefiles' \ -DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \ -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always \ -DCMAKE_TOOLCHAIN_FILE="$root/contrib/cross/mingw64.cmake" \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_STATIC_DEPS=ON \ -DLOKINET_PACKAGE=ON \ -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TESTING=OFF \ -DLOKINET_TESTS=OFF \ -DLOKINET_BOOTSTRAP=OFF \ -DLOKINET_NATIVE_BUILD=OFF \ -DSTATIC_LINK=ON \ -DWITH_SYSTEMD=OFF \ -DFORCE_OXENMQ_SUBMODULE=ON \ -DFORCE_OXENC_SUBMODULE=ON \ -DFORCE_FMT_SUBMODULE=ON \ -DFORCE_SPDLOG_SUBMODULE=ON \ -DFORCE_NLOHMANN_SUBMODULE=ON \ -DWITH_LTO=OFF \ "$@" ================================================ FILE: contrib/windows.sh ================================================ #!/bin/bash # # helper script for me for when i cross compile for windows # t. jeff # set -e set +x root="$(readlink -f $(dirname $0)/../)" mkdir -p $root/build/win32 $root/contrib/windows-configure.sh $root $root/build/win32 "$@" make package -j${JOBS:-$(nproc)} -C $root/build/win32 ================================================ FILE: daemon/CMakeLists.txt ================================================ set(exetargets lokinet) add_library(lokinet_daemon STATIC utils.cpp) target_link_libraries(lokinet_daemon PUBLIC liblokinet) if(APPLE) add_executable(lokinet lokinet.swift) target_compile_options(lokinet BEFORE PRIVATE -target x86_64-apple-macos${CMAKE_OSX_DEPLOYMENT_TARGET}) else() add_executable(lokinet lokinet.cpp) endif() add_executable(lokinet-cntrl lokinet-cntrl.cpp) enable_lto(lokinet lokinet-cntrl) list(APPEND exetargets lokinet-cntrl) set(should_install ON) set(SETCAP) if(CMAKE_SYSTEM_NAME MATCHES "Linux") option(WITH_SETCAP "use setcap when installing" ON) if(WITH_SETCAP) find_program(SETCAP NAMES setcap HINTS /sbin /usr/sbin) if(SETCAP) message(STATUS "Found setcap binary: ${SETCAP}") else() message(WARNING "cannot find setcap binary you will not be able use the install targets unless you use -DWITH_SETCAP=OFF") set(should_install OFF) endif() endif() endif() # cmake interface library for bunch of cmake hacks to fix final link order add_library(hax_and_shims_for_cmake INTERFACE) if(WIN32) target_link_libraries(hax_and_shims_for_cmake INTERFACE uvw oxenmq::oxenmq -lws2_32 -lshlwapi -ldbghelp -luser32 -liphlpapi -lpsapi -luserenv) endif() foreach(exe ${exetargets}) if(WIN32) target_sources(${exe} PRIVATE ${CMAKE_BINARY_DIR}/${exe}.rc) target_link_libraries(${exe} PRIVATE -static-libstdc++ -static-libgcc --static -Wl,--pic-executable,-e,mainCRTStartup,--subsystem,console:5.00) elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") target_link_directories(${exe} PRIVATE /usr/local/lib) endif() target_link_libraries(${exe} PRIVATE lokinet_daemon) if(LOKINET_STRIP) add_custom_command(TARGET ${exe} POST_BUILD COMMAND ${CMAKE_OBJCOPY} ARGS --only-keep-debug $ $.debug COMMAND ${CMAKE_STRIP} ARGS --strip-all $) endif() target_include_directories(${exe} PUBLIC "${PROJECT_SOURCE_DIR}") if(should_install) if(APPLE) install(TARGETS ${exe} BUNDLE DESTINATION "${PROJECT_BINARY_DIR}" RUNTIME DESTINATION "." COMPONENT lokinet) else() install(TARGETS ${exe} RUNTIME DESTINATION bin COMPONENT lokinet) endif() endif() endforeach() target_link_libraries(lokinet PRIVATE CLI11) target_link_libraries(lokinet-cntrl PRIVATE CLI11) if(WITH_SETCAP) install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)") endif() if(LOKINET_STRIP) add_custom_target(symbols ALL COMMAND ${CMAKE_COMMAND} -E tar cJf ${CMAKE_CURRENT_BINARY_DIR}/debug-symbols.tar.xz $.debug DEPENDS lokinet) endif() ================================================ FILE: daemon/lokinet-cntrl.cpp ================================================ #include "utils.hpp" #include #include #include using namespace llarp; using namespace nlohmann; namespace { /** Startup CLI options: - verbose - config file pathways - log level - loki controller RPC client URL Runtime CLI subcommands: - list - refresh - instance - init - status - close - stop */ struct cli_opts { bool verbose{false}; std::vector rpc_paths{ {"tcp://127.0.0.1:1190"}, {"tcp://127.0.0.1:1189"}, {"tcp://127.0.0.1:1188"}}; omq::address rpc_url{}; std::string log_level{"info"}; log::Level oxen_log_level{log::Level::info}; }; struct app_data { std::mutex m; std::deque input_que{}; std::condition_variable cv; std::atomic running{false}; }; } // namespace namespace { static std::shared_ptr runtime_data; template static int exit_now(bool is_error, fmt::format_string format, T&&... args) { if (is_error) log::error(controller::logcat, format, std::forward(args)...); else log::info(controller::logcat, format, std::forward(args)...); runtime_data->running = false; runtime_data->cv.notify_all(); return is_error ? 1 : 0; } auto prefigure = []() -> int { if (not runtime_data) runtime_data = std::make_shared(); return 0; }; static void app_loop(cli_opts&& options, std::promise&& p) { controller::rpc_controller rpc; if (not rpc.start(options.rpc_paths)) { log::critical(controller::logcat, "RPC controller failed to bind; exiting..."); p.set_value_at_thread_exit(); return; } size_t index{}; std::string address{}; std::string pubkey{}; CLI::App app{}; auto app_fmt = app.get_formatter(); app_fmt->column_width(40); app.set_help_flag(""); // inner app options auto* hcom = app.add_subcommand("help", "Print help menu")->silent(); hcom->callback([&]() { app.clear(); std::cout << app.help("", CLI::AppFormatMode::Normal) << std::endl; for (auto& com : app.get_subcommands(nullptr)) { std::cout << com->help("", CLI::AppFormatMode::Sub) << std::endl; for (auto& c : com->get_subcommands(nullptr)) std::cout << c->help("", CLI::AppFormatMode::Sub) << std::endl; } }) ->immediate_callback(); auto* list_subcom = app.add_subcommand("list", "List all lokinet instances currently running on the local machine"); list_subcom->callback([&]() { rpc.list_all(); })->immediate_callback(); auto* refresh_subcom = app.add_subcommand("refresh", "Refresh local lokinet instance information"); refresh_subcom->callback([&]() { rpc.refresh(); }); auto* instance_subcom = app.add_subcommand("instance", "Select a lokinet instance")->require_option(1)->require_subcommand(1); auto* aopt = instance_subcom->add_option("-a, --address", address, "Local RPC address of lokinet instance") ->type_name("IP:PORT"); auto* iopt = instance_subcom->add_option("-i, --index", index, "Index of local lokinet instance (use 'list' to query!)"); aopt->excludes(iopt); iopt->excludes(aopt); auto* init_subcom = instance_subcom->add_subcommand("init", "Initiate session to a remote instance")->require_option(1); init_subcom->add_option("-p, --pubkey", pubkey, "PubKey of remote lokinet instance"); init_subcom->callback([&]() { if (not address.empty()) rpc.initiate(omq::address{std::move(address)}, std::move(pubkey)); else rpc.initiate(index, std::move(pubkey)); }); auto* status_subcom = instance_subcom->add_subcommand("status", "Query status of local lokinet instance"); status_subcom->callback([&]() { if (not address.empty()) rpc.status(omq::address{std::move(address)}); else rpc.status(index); }); auto* close_subcom = instance_subcom->add_subcommand("close", "Close session to a remote instance")->require_option(1); close_subcom->add_option("-p, --pubkey", pubkey, "PubKey of remote lokinet instance"); close_subcom->callback([&]() { if (not address.empty()) rpc.close(omq::address{std::move(address)}, std::move(pubkey)); else rpc.close(index, std::move(pubkey)); }); auto* halt_subcom = instance_subcom->add_subcommand("halt", "Immediately halt lokinet instance"); halt_subcom->callback([&]() { if (not address.empty()) rpc.halt(omq::address{std::move(address)}); else rpc.halt(index); }); // notify startup successful runtime_data->running = true; p.set_value(); while (runtime_data->running) { try { std::deque copy{}; { std::unique_lock lock{runtime_data->m, std::defer_lock}; runtime_data->cv.wait( lock, []() { return !runtime_data->input_que.empty() || !runtime_data->running; }); copy.swap(runtime_data->input_que); } if (!copy.empty()) { log::debug(controller::logcat, "processing input..."); while (!copy.empty()) { auto line = copy.front(); copy.pop_front(); log::debug(controller::logcat, "input: {}", line); app.parse(line); } } } catch (const std::exception& e) { log::warning(controller::logcat, "Exception: {}", e.what()); } app.clear(); } } static void input_loop() { log::info(controller::logcat, "input loop started..."); std::string input; while (runtime_data->running) { std::getline(std::cin, input); if (input == "exit") { runtime_data->running = false; runtime_data->cv.notify_all(); break; } { std::lock_guard lock{runtime_data->m}; runtime_data->input_que.push_back(std::move(input)); } log::debug(controller::logcat, "dispatched..."); runtime_data->cv.notify_all(); } log::info(controller::logcat, "input loop exiting..."); } } // namespace int main(int argc, char* argv[]) { if (auto rv = prefigure(); rv != 0) return rv; CLI::App cli{"loki controller - lokinet instance control utility", "lokinet-cntrl"}; cli.get_formatter()->column_width(50); cli_opts options{}; // initial options cli.add_flag("-v, --verbose", options.verbose, "Verbose logging (equivalent to passing '--log-level=debug')"); cli.add_option( "-r, --rpc", options.rpc_paths, "Specify RPC bind addresses for loki controller to connect to (accepts multiple args)") ->type_name("PATH(S)") ->capture_default_str(); cli.add_option( "-l, --log-level", options.log_level, "Log verbosity level ('error', 'warn', 'info', 'debug', 'trace')") ->type_name("LEVEL") ->capture_default_str(); try { cli.parse(argc, argv); } catch (const CLI::ParseError& e) { return exit_now(true, "Exception: {}", e.what()); } catch (const std::exception& e) { return exit_now(true, "Exception: {}", e.what()); } options.oxen_log_level = log::level_from_string(options.log_level); if (options.verbose) options.oxen_log_level = log::Level::debug; log::add_sink(log::Type::Print, "stderr"); log::reset_level(options.oxen_log_level); log::info(controller::logcat, "initializing..."); try { std::promise p; auto f = p.get_future(); std::thread app_thread{app_loop, std::move(options), std::move(p)}; f.get(); std::thread input_thread{input_loop}; app_thread.join(); input_thread.join(); } catch (const std::exception& e) { return exit_now(true, "Exception: {}", e.what()); } log::info(controller::logcat, "exiting..."); return 0; } ================================================ FILE: daemon/lokinet.cpp ================================================ #include #include // for ensure_config #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif namespace { struct command_line_options { // bool options bool help = false; bool version = false; bool generate = false; bool generate_embedded = false; bool router = false; bool config = false; bool overwrite = false; // string options // TODO: change this to use a std::filesystem::path once we stop using ghc::filesystem on // some platforms std::string configPath; // windows options bool win_install = false; bool win_remove = false; }; // windows-specific function declarations int startWinsock(); void install_win32_daemon(); void uninstall_win32_daemon(); // operational function definitions int lokinet_main(int, char**); void handle_signal(int sig); static void start_lokinet(std::optional confFile, bool snode); // variable declarations static auto logcat = llarp::log::Cat("daemon"); std::unique_ptr ctx; // operational function definitions void handle_signal(int sig) { llarp::log::info(logcat, "Handling signal {}", sig); if (ctx) ctx->signal(sig); else std::cerr << "Received signal " << sig << ", but have no context yet. Ignoring!" << std::endl; } // Windows specific code #ifdef _WIN32 extern "C" LONG FAR PASCAL win32_signal_handler(EXCEPTION_POINTERS*); extern "C" VOID FAR PASCAL win32_daemon_entry(DWORD, LPTSTR*); VOID insert_description(); extern "C" BOOL FAR PASCAL handle_signal_win32(DWORD fdwCtrlType) { UNREFERENCED_PARAMETER(fdwCtrlType); handle_signal(SIGINT); return TRUE; // probably unreachable }; int startWinsock() { WSADATA wsockd; int err; err = ::WSAStartup(MAKEWORD(2, 2), &wsockd); if (err) { perror("Failed to start Windows Sockets"); return err; } ::CreateMutex(nullptr, FALSE, "lokinet_win32_daemon"); return 0; } void install_win32_daemon() { SC_HANDLE schSCManager; SC_HANDLE schService; std::array szPath{}; if (!GetModuleFileName(nullptr, szPath.data(), MAX_PATH)) { llarp::log::error(logcat, "Cannot install service {}", GetLastError()); return; } // Get a handle to the SCM database. schSCManager = OpenSCManager( nullptr, // local computer nullptr, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights if (nullptr == schSCManager) { llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); return; } // Create the service schService = CreateService( schSCManager, // SCM database strdup("lokinet"), // name of service "Lokinet for Windows", // service name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_DEMAND_START, // start type SERVICE_ERROR_NORMAL, // error control type szPath.data(), // path to service's binary nullptr, // no load ordering group nullptr, // no tag identifier nullptr, // no dependencies nullptr, // LocalSystem account nullptr); // no password if (schService == nullptr) { llarp::log::error(logcat, "CreateService failed {}", GetLastError()); CloseServiceHandle(schSCManager); return; } else llarp::log::info(logcat, "Service installed successfully"); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); insert_description(); } VOID insert_description() { SC_HANDLE schSCManager; SC_HANDLE schService; SERVICE_DESCRIPTION sd; LPTSTR szDesc = strdup( "LokiNET is a free, open source, private, " "decentralized, \"market based sybil resistant\" " "and IP based onion routing network"); // Get a handle to the SCM database. schSCManager = OpenSCManager( NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights if (nullptr == schSCManager) { llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); return; } // Get a handle to the service. schService = OpenService( schSCManager, // SCM database "lokinet", // name of service SERVICE_CHANGE_CONFIG); // need change config access if (schService == nullptr) { llarp::log::error(logcat, "OpenService failed {}", GetLastError()); CloseServiceHandle(schSCManager); return; } // Change the service description. sd.lpDescription = szDesc; if (!ChangeServiceConfig2( schService, // handle to service SERVICE_CONFIG_DESCRIPTION, // change: description &sd)) // new description { llarp::log::error(logcat, "ChangeServiceConfig2 failed"); } else llarp::log::info(log_cat, "Service description updated successfully."); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); } void uninstall_win32_daemon() { SC_HANDLE schSCManager; SC_HANDLE schService; // Get a handle to the SCM database. schSCManager = OpenSCManager( nullptr, // local computer nullptr, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights if (nullptr == schSCManager) { llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); return; } // Get a handle to the service. schService = OpenService( schSCManager, // SCM database "lokinet", // name of service 0x10000); // need delete access if (schService == nullptr) { llarp::log::error(logcat, "OpenService failed {}", GetLastError()); CloseServiceHandle(schSCManager); return; } // Delete the service. if (!DeleteService(schService)) { llarp::log::error(logcat, "DeleteService failed {}", GetLastError()); } else llarp::log::info(logcat, "Service deleted successfully"); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); } /// minidump generation for windows jizz /// will make a coredump when there is an unhandled exception LONG GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) { const auto flags = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo); const std::string fname = fmt::format("C:\\ProgramData\\lokinet\\crash-{}.dump", llarp::time_now_ms().count()); HANDLE hDumpFile; SYSTEMTIME stLocalTime; GetLocalTime(&stLocalTime); MINIDUMP_EXCEPTION_INFORMATION ExpParam{}; hDumpFile = CreateFile( fname.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); ExpParam.ExceptionPointers = pExceptionPointers; ExpParam.ClientPointers = TRUE; MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, flags, &ExpParam, NULL, NULL); return 1; } VOID FAR PASCAL SvcCtrlHandler(DWORD dwCtrl) { // Handle the requested control code. switch (dwCtrl) { case SERVICE_CONTROL_STOP: // tell service we are stopping llarp::log::debug(logcat, "Windows service controller gave SERVICE_CONTROL_STOP"); llarp::sys::service_manager->system_changed_our_state(llarp::sys::ServiceState::Stopping); handle_signal(SIGINT); return; case SERVICE_CONTROL_INTERROGATE: // report status llarp::log::debug(logcat, "Got win32 service interrogate signal"); llarp::sys::service_manager->report_changed_state(); return; default: llarp::log::debug(logcat, "Got win32 unhandled signal {}", dwCtrl); break; } } // The win32 daemon entry point is where we go when invoked as a windows service; we do the // required service dance and then pretend we were invoked via main(). VOID FAR PASCAL win32_daemon_entry(DWORD, LPTSTR* argv) { // Register the handler function for the service auto* svc = dynamic_cast(llarp::sys::service_manager); svc->handle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler); if (svc->handle == nullptr) { llarp::log::error(logcat, "failed to register daemon control handler"); return; } // we hard code the args to lokinet_main. // we yoink argv[0] (lokinet.exe path) and pass in the new args. std::array args = { reinterpret_cast(argv[0]), reinterpret_cast(strdup("c:\\programdata\\lokinet\\lokinet.ini")), reinterpret_cast(0)}; lokinet_main(args.size() - 1, args.data()); } #endif int lokinet_main(int argc, char** argv) { #ifdef _WIN32 if (startWinsock()) return -1; SetConsoleCtrlHandler(handle_signal_win32, TRUE); #endif CLI::App cli{ "Lokinet is a free, open source, private, decentralized, market-based sybil resistant " "and IP based onion routing network lokinet"}; command_line_options options{}; // flags: boolean values in command_line_options struct cli.add_flag("--version", options.version, "Lokinet version"); cli.add_flag("-g,--generate", options.generate, "Generate default configuration and exit"); cli.add_flag("-r,--router", options.router, "Run lokinet in routing mode instead of client-only mode"); cli.add_flag( "-e,--generate-embedded", options.generate_embedded, "Generate a default config file for an embedded clients and exit"); cli.add_flag("-f,--force", options.overwrite, "Force writing config even if file exists"); // options: string cli.add_option("config,--config", options.configPath, "Path to lokinet.ini configuration file") ->capture_default_str(); if constexpr (llarp::platform::is_windows) { cli.add_flag("--install", options.win_install, "Install win32 daemon to SCM"); cli.add_flag("--remove", options.win_remove, "Remove win32 daemon from SCM"); } try { cli.parse(argc, argv); } catch (const CLI::ParseError& e) { return cli.exit(e); } std::optional configFile; try { if (options.version) { std::cout << llarp::LOKINET_VERSION_FULL << std::endl; return 0; } if constexpr (llarp::platform::is_windows) { if (options.win_install) { install_win32_daemon(); return 0; } if (options.win_remove) { uninstall_win32_daemon(); return 0; } } if (not options.configPath.empty()) { configFile = options.configPath; } } catch (const CLI::OptionNotFound& e) { cli.exit(e); } catch (const CLI::Error& e) { cli.exit(e); } auto type = options.generate_embedded ? llarp::config::Type::EmbeddedClient : options.router ? llarp::config::Type::Relay : llarp::config::Type::FullClient; if (configFile.has_value()) { // when we have an explicit filepath std::filesystem::path basedir = configFile->parent_path(); if (options.generate || options.generate_embedded) { try { llarp::ensure_config(basedir, *configFile, options.overwrite, type); } catch (std::exception& ex) { llarp::log::error(logcat, "cannot generate config at {}: {}", *configFile, ex.what()); return 1; } } else { try { if (!exists(*configFile)) { llarp::log::error(logcat, "Config file not found {}", *configFile); return 1; } } catch (std::exception& ex) { llarp::log::error(logcat, "cannot check if ", *configFile, " exists: ", ex.what()); return 1; } } } else { try { llarp::ensure_config( llarp::GetDefaultDataDir(), llarp::GetDefaultConfigPath(), options.overwrite, type); } catch (std::exception& ex) { llarp::log::error(logcat, "cannot ensure config: {}", ex.what()); return 1; } configFile = llarp::GetDefaultConfigPath(); } if (options.generate || options.generate_embedded) return 0; #ifdef _WIN32 SetUnhandledExceptionFilter(&GenerateDump); #endif try { start_lokinet(configFile, options.router); } catch (const std::exception& e) { std::cerr << "\nLokinet failed to start: " << e.what() << "\n\n"; return 1; } std::promise watchdog_stop; std::thread watchdog{[ftr = watchdog_stop.get_future()] { llarp::util::SetThreadName("llarp-watchdog"); while (ftr.wait_for(1s) != std::future_status::ready) { // do periodic non lokinet related tasks here if (ctx and ctx->is_up() and not ctx->looks_alive()) { auto deadlock_cat = llarp::log::Cat("deadlock"); llarp::log::critical(deadlock_cat, "Router has deadlocked!"); llarp::log::flush(); llarp::sys::service_manager->failed(); std::abort(); } } }}; ctx->wait(); watchdog_stop.set_value(); watchdog.join(); llarp::log::flush(); llarp::sys::service_manager->stopped(); ctx.reset(); return 0; } // this sets up, configures and runs the main context static void start_lokinet(std::optional confFile, bool snode) { llarp::log::info(logcat, "starting up {}", llarp::LOKINET_VERSION_FULL); try { auto type = snode ? llarp::config::Type::Relay : llarp::config::Type::FullClient; std::optional conf; try { conf = confFile ? llarp::Config{type, *confFile} : llarp::Config{type, "", llarp::GetDefaultDataDir()}; } catch (const std::exception& e) { llarp::log::error(logcat, "Failed to load config: {}", e.what()); throw; } ctx = std::make_unique(/*embedded=*/false); signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); signal(SIGKILL, handle_signal); llarp::util::SetThreadName("llarp-main"); ctx->start(std::move(*conf)); } catch (llarp::util::bind_socket_error& ex) { auto msg = "{}; is lokinet already running?"_format(ex.what()); llarp::log::error(logcat, "{}", msg); throw std::runtime_error{msg}; } catch (const std::exception& ex) { // Don't need to log here: context has already error logged the exception message throw; } } } // namespace int main(int argc, char* argv[]) { // Set up a default, stderr logging for very early logging; we'll replace this later once we // read the desired log info from config. oxen::log::add_sink(llarp::log::Type::Print, "stderr"); oxen::log::reset_level(llarp::log::Level::info); // oxen::log::set_level("quic", oxen::log::Level::info); // TODO FIXME: this seems to be segfaulting? // llarp::logRingBuffer = std::make_shared(100); // oxen::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); #ifndef _WIN32 return lokinet_main(argc, argv); #else if (auto hntdll = GetModuleHandle("ntdll.dll")) { if (GetProcAddress(hntdll, "wine_get_version")) { static const char* text = "Don't run lokinet in wine, aborting startup"; static const char* title = "Lokinet Wine Error"; MessageBoxA(NULL, text, title, MB_ICONHAND); std::abort(); } } SERVICE_TABLE_ENTRY DispatchTable[] = { {strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}}; // Try first to run as a service; if this works it fires off to win32_daemon_entry and doesn't // return until the service enters STOPPED state. if (StartServiceCtrlDispatcher(DispatchTable)) return 0; auto error = GetLastError(); // We'll get this error if not invoked as a service, which is fine: we can just run directly if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { llarp::sys::service_manager->disable(); return lokinet_main(argc, argv); } else { llarp::log::critical(logcat, "Error launching service: {}", std::system_category().message(error)); return 1; } #endif } ================================================ FILE: daemon/lokinet.swift ================================================ import AppKit import Foundation import NetworkExtension import SystemExtensions let app = NSApplication.shared let START = "--start" let STOP = "--stop" let HELP_STRING = "usage: lokinet {--start|--stop}" class LokinetMain: NSObject, NSApplicationDelegate { var vpnManager = NETunnelProviderManager() var mode = START let netextBundleId = "org.lokinet.network-extension" func applicationDidFinishLaunching(_: Notification) { if mode == START { startNetworkExtension() } else if mode == STOP { tearDownVPNTunnel() } else { result(msg: HELP_STRING) } } func bail() { app.terminate(self) } func result(msg: String) { NSLog(msg) // TODO: does lokinet continue after this? bail() } func tearDownVPNTunnel() { NSLog("Stopping Lokinet") NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in if let error = error { self.result(msg: error.localizedDescription) return } if let savedManagers = savedManagers { for manager in savedManagers { if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId { manager.connection.stopVPNTunnel() self.result(msg: "Lokinet Down") } } } self.result(msg: "Lokinet is not up") } } func startNetworkExtension() { #if MACOS_SYSTEM_EXTENSION NSLog("Loading Lokinet network extension") // Start by activating the system extension let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: netextBundleId, queue: .main) activationRequest.delegate = self OSSystemExtensionManager.shared.submitRequest(activationRequest) #else setupVPNTunnel() #endif } func setupVPNTunnel() { NSLog("Starting up Lokinet tunnel") NETunnelProviderManager.loadAllFromPreferences { [self] (savedManagers: [NETunnelProviderManager]?, error: Error?) in if let error = error { self.result(msg: error.localizedDescription) return } if let savedManagers = savedManagers { for manager in savedManagers { if (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.netextBundleId { NSLog("Found saved VPN Manager") self.vpnManager = manager } } } let providerProtocol = NETunnelProviderProtocol() providerProtocol.serverAddress = "loki.loki" // Needs to be set to some non-null dummy value providerProtocol.username = "anonymous" providerProtocol.providerBundleIdentifier = self.netextBundleId if #available(macOS 11, *) { providerProtocol.enforceRoutes = true } // macos seems to have trouble when this is true, and reports are that this breaks and // doesn't do what it says on the tin in the first place. Needs more testing. providerProtocol.includeAllNetworks = false self.vpnManager.protocolConfiguration = providerProtocol self.vpnManager.isEnabled = true // self.vpnManager.isOnDemandEnabled = true self.vpnManager.localizedDescription = "lokinet" self.vpnManager.saveToPreferences(completionHandler: { [self] error -> Void in if error != nil { NSLog("Error saving to preferences") self.result(msg: error!.localizedDescription) } else { self.vpnManager.loadFromPreferences(completionHandler: { error in if error != nil { NSLog("Error loading from preferences") self.result(msg: error!.localizedDescription) } else { do { NSLog("Trying to start") self.initializeConnectionObserver() try self.vpnManager.connection.startVPNTunnel() } catch let error as NSError { self.result(msg: error.localizedDescription) } catch { self.result(msg: "There was a fatal error") } // Check if we are already connected because, if so, we won't get a // status change and will just hang waiting for one. if self.vpnManager.connection.status == .connected { self.result(msg: "VPN already connected"); } } }) } }) } } func initializeConnectionObserver() { NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: vpnManager.connection, queue: OperationQueue.main) { [self] _ -> Void in if self.vpnManager.connection.status == .invalid { self.result(msg: "VPN configuration is invalid") } else if self.vpnManager.connection.status == .disconnected { self.result(msg: "VPN is disconnected.") } else if self.vpnManager.connection.status == .connecting { NSLog("VPN is connecting...") } else if self.vpnManager.connection.status == .reasserting { NSLog("VPN is reasserting...") } else if self.vpnManager.connection.status == .disconnecting { NSLog("VPN is disconnecting...") } else if self.vpnManager.connection.status == .connected { self.result(msg: "VPN Connected") } } } } #if MACOS_SYSTEM_EXTENSION extension LokinetMain: OSSystemExtensionRequestDelegate { func request(_: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { guard result == .completed else { NSLog("Unexpected result %d for system extension request", result.rawValue) return } NSLog("Lokinet system extension loaded") setupVPNTunnel() } func request(_: OSSystemExtensionRequest, didFailWithError error: Error) { NSLog("System extension request failed: %@", error.localizedDescription) self.bail() } func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { NSLog("Extension %@ requires user approval", request.identifier) } func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension extension: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { NSLog("Replacing extension %@ version %@ with version %@", request.identifier, existing.bundleShortVersion, `extension`.bundleShortVersion) return .replace } } #endif let args = CommandLine.arguments // If we are invoked with no arguments then exec the gui. This is dumb, but there doesn't seem to // be a nicer way to do this on Apple's half-baked platform because: // - we have three "bundles" we need to manage: the GUI app, the system extension, and the Lokinet // app (this file) which loads the system extension. // - if we embed the system extension directly inside the GUI then it fails to launch because the // electron GUI's requirements (needed for JIT) conflict with the ability to load a system // extensions. // - if we embed Lokinet.app inside Lokinet-GUI.app and then the system extension inside Lokinet.app // then it works, but macos loses track of the system extension and doesn't remove it when you // remove the application. (It breaks your system, leaving an impossible-to-remove system // extension, in just the same way it breaks if you don't use Finder to remove the Application. // Apple used to say (around 2 years ago as of writing) that they would fix this situation "soon", // but hasn't, and has stopped saying anything about it.) // - if we try to use multiple executables (one to launch the system extension, one simple shell // script to execs the embedded GUI app) inside the Lokinet.app and make the GUI the default for // the application then Lokinet gets killed by gatekeeper because code signing only applies the // (required-for-system-extensions) provisioningprofile to the main binary in the app. // // So we are left needing *one* single binary that isn't the GUI but has to do double-duty for both // exec'ing the binary and loading lokinet, depending on how it is called. // // But of course there is no way to specify command-line arguments to the default binary macOS runs, // so we can't use a `--gui` flag or anything so abhorrent to macos purity, thus this nasty // solution: // - no args -- exec the GUI // - `--start` -- load the system extension and start lokinet // - `--stop` -- stop lokinet // // macOS: land of half-baked implementations and nasty hacks to make anything work. if args.count == 1 { let gui_path = Bundle.main.resourcePath! + "/../Helpers/Lokinet-GUI.app" if !FileManager.default.fileExists(atPath: gui_path) { NSLog("Could not find gui app at %@", gui_path) exit(1) } let gui_url = URL(fileURLWithPath: gui_path, isDirectory: false) let gui_app_conf = NSWorkspace.OpenConfiguration() let group = DispatchGroup() group.enter() NSWorkspace.shared.openApplication(at: gui_url, configuration: gui_app_conf, completionHandler: { (app, error) in if error != nil { NSLog("Error launching gui: %@", error!.localizedDescription) } else { NSLog("Lauched GUI"); } group.leave() }) group.wait() } else if args.count == 2 { let delegate = LokinetMain() delegate.mode = args[1] app.delegate = delegate app.run() } else { NSLog(HELP_STRING) } ================================================ FILE: daemon/utils.cpp ================================================ #include "utils.hpp" #include #include namespace llarp::controller { size_t lokinet_instance::next_id = 0; rpc_controller::rpc_controller() : _omq{std::make_shared()} {} void rpc_controller::_initiate(omq::address src, std::string remote) { log::info( logcat, "Instructing lokinet instance (bind:{}) to initiate session to remote:{}", src.full_address(), remote); nlohmann::json req; req["pk"] = remote; if (auto it = _binds.find(src); it != _binds.end()) _omq->request( it->second.cid, "llarp.session_init", [&](bool success, std::vector data) { if (success) { auto res = nlohmann::json::parse(data[0]); log::info(logcat, "RPC call to initiate session succeeded: {}", res.dump()); } else log::critical(logcat, "RPC call to initiate session failed!"); }, req.dump()); else log::critical( logcat, "Could not find connection ID to RPC bind {} for `session_init` command", src.full_address()); } void rpc_controller::_status(omq::address src) { log::info(logcat, "Querying lokinet instance (bind:{}) for router status", src.full_address()); if (auto it = _binds.find(src); it != _binds.end()) _omq->request(it->second.cid, "llarp.status", [&](bool success, std::vector data) { if (success) { auto res = nlohmann::json::parse(data[0]); log::info(logcat, "RPC call to query router status succeeded: \n{}\n", res.dump(4)); } else log::critical(logcat, "RPC call to query router status failed!"); }); else log::critical( logcat, "Could not find connection ID to RPC bind {} for `status` command", src.full_address()); } void rpc_controller::_close(omq::address src, std::string remote) { log::info( logcat, "Querying lokinet instance (bind:{}) to close session to remote:{}", src.full_address(), remote); nlohmann::json req; req["pk"] = remote; if (auto it = _binds.find(src); it != _binds.end()) _omq->request( it->second.cid, "llarp.session_close", [&](bool success, std::vector data) { if (success) { auto res = nlohmann::json::parse(data[0]); log::info(logcat, "RPC call to close session succeeded: {}", res.dump()); } else log::critical(logcat, "RPC call to close session failed!"); }, req.dump()); else log::critical( logcat, "Could not find connection ID to RPC bind {} for `session_close` command", src.full_address()); } void rpc_controller::_halt(omq::address src) { log::info(logcat, "Instructing lokinet instance (bind:{}) to halt", src.full_address()); if (auto it = _binds.find(src); it != _binds.end()) { _omq->request(it->second.cid, "llarp.halt", [&](bool success, std::vector data) { if (success) { auto res = nlohmann::json::parse(data[0]); log::info(logcat, "RPC call to halt instance succeeded: {}", res.dump()); } else log::critical(logcat, "RPC call to halt instance failed!"); }); } else log::critical(logcat, "Could not find connection ID to RPC bind {} for `halt` command", src.full_address()); } bool rpc_controller::_omq_connect(const std::vector& bind_addrs) { int i = 0; std::vector> connect_proms{bind_addrs.size()}; for (auto& b : bind_addrs) { omq::address bind{b}; log::info(logcat, "RPC controller connecting to RPC bind address ({})", bind.full_address()); auto cid = _omq->connect_remote( bind, [&, idx = i](auto) { log::info(logcat, "Loki controller successfully connected to RPC bind ({})", bind.full_address()); connect_proms[idx].set_value(true); }, [&, idx = i](auto, std::string_view msg) { log::info( logcat, "Loki controller failed to connect to RPC bind ({}): {}", bind.full_address(), msg); connect_proms[idx].set_value(false); }); auto it = _binds.emplace(bind, lokinet_instance{cid}).first; _indexes.emplace(it->second.ID, it->first); i += 1; } bool ret = true; for (auto& p : connect_proms) ret &= p.get_future().get(); return ret; } bool rpc_controller::start(std::vector& bind_addrs) { _omq->start(); return _omq_connect(bind_addrs); } void rpc_controller::list_all() const { auto msg = "\n\n\tLokinet RPC controller connected to {} RPC binds:\n"_format(_binds.size()); for (auto& [idx, addr] : _indexes) msg += "\t\tID:{} | Address:{}\n"_format(idx, addr.full_address()); log::info(logcat, "{}", msg); } void rpc_controller::refresh() { log::critical(logcat, "TODO: implement this!"); } void rpc_controller::initiate(size_t idx, std::string remote) { if (auto it = _indexes.find(idx); it != _indexes.end()) _initiate(it->second, std::move(remote)); else log::warning(logcat, "Could not find instance with given index: {}", idx); } void rpc_controller::initiate(omq::address src, std::string remote) { return _initiate(std::move(src), std::move(remote)); } void rpc_controller::status(omq::address src) { return _status(std::move(src)); }; void rpc_controller::status(size_t idx) { if (auto it = _indexes.find(idx); it != _indexes.end()) _status(it->second); else log::warning(logcat, "Could not find instance with given index: {}", idx); } void rpc_controller::close(omq::address src, std::string remote) { return _close(std::move(src), std::move(remote)); } void rpc_controller::close(size_t idx, std::string remote) { if (auto it = _indexes.find(idx); it != _indexes.end()) _close(it->second, std::move(remote)); else log::warning(logcat, "Could not find instance with given index: {}", idx); } void rpc_controller::halt(omq::address src) { return _halt(std::move(src)); } void rpc_controller::halt(size_t idx) { if (auto it = _indexes.find(idx); it != _indexes.end()) _halt(it->second); else log::warning(logcat, "Could not find instance with given index: {}", idx); } } // namespace llarp::controller ================================================ FILE: daemon/utils.hpp ================================================ #pragma once #include #include #include #include namespace omq = oxenmq; namespace llarp::controller { static auto logcat = log::Cat("rpc-controller"); struct rpc_controller; struct lokinet_instance { friend struct rpc_controller; private: static size_t next_id; public: lokinet_instance(omq::ConnectionID c) : ID{++next_id}, cid{std::move(c)} {} const size_t ID; omq::ConnectionID cid; }; struct rpc_controller { rpc_controller(); private: std::shared_ptr _omq; std::unordered_map _binds; std::map _indexes; void _initiate(omq::address src, std::string remote); void _status(omq::address src); void _close(omq::address src, std::string remote); void _halt(omq::address src); bool _omq_connect(const std::vector& bind_addrs); public: bool start(std::vector& bind_addrs); void list_all() const; void refresh(); void initiate(omq::address src, std::string remote); void initiate(size_t idx, std::string remote); void status(omq::address src); void status(size_t idx); void close(omq::address src, std::string remote); void close(size_t idx, std::string remote); void halt(omq::address src); void halt(size_t idx); }; } // namespace llarp::controller ================================================ FILE: docs/CMakeLists.txt ================================================ find_program(DOXYGEN doxygen) if (NOT DOXYGEN) message(STATUS "Documentation generation disabled (doxygen not found)") return() endif() find_program(MKDOCS mkdocs) if (NOT MKDOCS) message(STATUS "Documentation generation disabled (mkdocs not found)") return() endif() set(lokinet_doc_sources "${DOCS_SRC}") string(REPLACE ";" " " lokinet_doc_sources_spaced "${lokinet_doc_sources}") add_custom_target(clean_xml COMMAND ${CMAKE_COMMAND} -E rm -rf doxyxml) add_custom_target(clean_markdown COMMAND ${CMAKE_COMMAND} -E rm -rf markdown) add_custom_command( OUTPUT doxyxml/index.xml COMMAND ${DOXYGEN} Doxyfile DEPENDS clean_xml ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ${lokinet_doc_sources} ) # find doxybook2 find_program(DOXYBOOK2 doxybook2) if(NOT DOXYBOOK2) if(NOT DOXYBOOK2_ZIP_URL) set(DOXYBOOK2_VERSION v1.4.0 CACHE STRING "doxybook2 version") set(DOXYBOOK2_ZIP_URL "https://github.com/matusnovak/doxybook2/releases/download/${DOXYBOOK2_VERSION}/doxybook2-linux-amd64-${DOXYBOOK2_VERSION}.zip") set(DOXYBOOK2_ZIP_HASH_OPTS EXPECTED_HASH SHA256=bab9356f5daa550cbf21d8d9b554ea59c8be039716a2caf6e96dee52713fccb0) endif() file(DOWNLOAD ${DOXYBOOK2_ZIP_URL} ${CMAKE_CURRENT_BINARY_DIR}/doxybook2.zip ${DOXYBOOK2_ZIP_HASH_OPTS}) execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_CURRENT_BINARY_DIR}/doxybook2.zip WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) set(DOXYBOOK2 ${CMAKE_CURRENT_BINARY_DIR}/bin/doxybook2) set(doxybook_localbin ${DOXYBOOK2}) endif() add_custom_command( OUTPUT gen COMMAND ${DOXYBOOK2} --input ${CMAKE_CURRENT_BINARY_DIR}/doxyxml --output ${CMAKE_CURRENT_BINARY_DIR}/gen --config config.json DEPENDS ${doxybook_localbin} ${CMAKE_CURRENT_BINARY_DIR}/gen/index.md ${CMAKE_CURRENT_BINARY_DIR}/config.json ${CMAKE_CURRENT_BINARY_DIR}/doxyxml/index.xml) add_custom_target(clean_html COMMAND ${CMAKE_COMMAND} -E rm -rf html) add_custom_command( OUTPUT markdown COMMAND find ${CMAKE_CURRENT_BINARY_DIR}/gen/ -type f -name '*.md' -exec ${CMAKE_CURRENT_SOURCE_DIR}/fix-markdown.sh {} "\;" && ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/gen ${CMAKE_CURRENT_BINARY_DIR}/markdown DEPENDS gen ) add_custom_command( OUTPUT html COMMAND ${MKDOCS} build DEPENDS clean_html ${CMAKE_CURRENT_BINARY_DIR}/markdown) add_custom_target(doc DEPENDS markdown) configure_file(Doxyfile.in Doxyfile @ONLY) configure_file(index.md.in index.md @ONLY) configure_file(config.json config.json COPYONLY) configure_file(mkdocs.yml mkdocs.yml COPYONLY) # we seperate this step out so we force clean_markdown to run before markdown target add_custom_command( OUTPUT gen/index.md COMMAND ${CMAKE_COMMAND} -E copy index.md gen/index.md DEPENDS clean_markdown) ================================================ FILE: docs/Doxyfile.in ================================================ PROJECT_NAME = "Lokinet" PROJECT_NUMBER = v@lokinet_VERSION@ PROJECT_BRIEF = "Anonymous, decentralized and IP based overlay network for the internet." OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ @PROJECT_BINARY_DIR@ JAVADOC_AUTOBRIEF = YES ALIASES = "rst=\verbatim embed:rst" ALIASES += "endrst=\endverbatim" BUILTIN_STL_SUPPORT = YES INPUT = @lokinet_doc_sources_spaced@ INCLUDE_PATH = @PROJECT_SOURCE_DIR@/include @PROJECT_SOURCE_DIR@/llarp @PROJECT_SOURCE_DIR@/external/ghc-filesystem/include/ RECURSIVE = YES CLANG_ASSISTED_PARSING = NO #CLANG_OPTIONS = -std=c++17 -Wno-pragma-once-outside-header GENERATE_HTML = NO HTML_OUTPUT = doxyhtml GENERATE_LATEX = NO GENERATE_XML = YES XML_OUTPUT = doxyxml MACRO_EXPANSION = YES ================================================ FILE: docs/LICENSE ================================================ Low Latency Anonymous Routing Protocol Specification Written in 2017 by Jeff Becker To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this protocol specfication to the public domain worldwide. This software is distributed without any warranty. You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . ================================================ FILE: docs/architecture.md ================================================ # High-Level Architecture ## Path Building

Starting from the top, here's a high-level overview of how the lokinet client builds a path to a terminating node 1. Client semi-randomly selects SN's for hops 2 and 3 using Introset Hash Ring (IHR) - First hop is sticky: upon initialization of lokinet, 4-5 first hops are selected 2. Message sent to hop 1 - Message consists of eight records in a linked list. Four hops are typically used, leaving the last 4 links as dummy records - Each record contains a TX (upstream) path ID and RX (downstream) path ID - Each record has a pointer to the next record, except for the final hops' record; the pointer here is recursive, signalling the end of the path-build 3. Hop 2 pops top record, appends metadata, and pushes record to the back of linked list - Hop adds metadata to the record, such as optional lifetime, pubkey to derive shared secret, etc 4. Steps 2-3 are repeated for the remaining hops until destination is reached - Final hop reads the recursive pointer signalling the end of the path-build process 5. Upon completion, plain-text reply is propagated backwards, where the client can then decrypt all records 6. Client measures latency - A) Routing message is sequentially encrypted using hop 4's key through hop 1's key - At each iteration, the nonce is permuted by XOR'ing the previous nonce with the hash of the secret key of each hop - B) Routing message is sent s.t. each hop can decrypt, with final hop receiving plain-text - Each hop appends latency and expiration time data, with the final hop interpreting the plain-text as a routing message and sending it back to the client 7. Introset is published to IHR upon successful completion; introset contains: - Path ID's of routers - Latency and expiration time for each hop - DNS SRV records - etc ### Failure Cases 1. Next hop is an invalid SN 2. Cannot connect to SN In either case, the path-build status is sent backwards with an error flag. Once received by the client, metadata related to the prospective path is wiped and the path forgotten ================================================ FILE: docs/config.json ================================================ { "baseUrl": "", "indexInFolders": false, "linkSuffix": ".md", "mainPageInRoot": false, "mainPageName": "index", "linkLowercase": false, "foldersToGenerate": ["files", "classes", "namespaces"] } ================================================ FILE: docs/dns-overview.md ================================================ # DNS in Lokinet Lokinet uses dns are its primary interface for resolving, mapping and querying resources inside of lokinet. This was done not because DNS is *good* protocol, but because there is almost no relevent userland applications that are incapable of interacting with DNS, across every platform. Using DNS in lokinet allows for the most zero config setup possible with the current set of standard protocols. Lokinet provides 2 internal gtld, `.loki` and `.snode` ## .snode The `.snode` gtld is used to address a lokinet router in the form of `.snode`. Traffic bound to a `.snode` tld will have its source authenticatable only if it originates from another valid lokinet router. Clients can also send traffic to and from addresses mapped to `.snode` addresses, but the source address on the service node side is ephemeral. In both cases, ip traffic to addresses mapped to `.snode` addresses will have the destination ip rewritten by the lokinet router to be its local interface ip, this ensures traffic stays on the lokinet router' interface for snode traffic and preventing usage as an exit node. ## .loki The `.loki` gtld is used to address anonymously published routes to lokinet clients on the network. ## What RR are provided? All `.loki` domains by default have the following dns rr synthesized by lokinet: * `A` record for initiating address mapping * `MX` record pointing to the synthesizesd `A` record * free wildcard entries for all of the above. Wildard entries are currently only pointing All `.snode` domains have by defult just an `A` record for initiating address mapping. Additionally both `.loki` and `.snode` can optionally provide multiple `SRV` records to advertise existence of services on or off of the name. ================================================ FILE: docs/doxygen.md ================================================ # Doxygen building doxygen docs requires the following: * cmake * doxygen * sphinx-build * sphinx readthedocs theme * breathe * exhale install packages: $ sudo apt install make cmake doxygen python3-sphinx python3-sphinx-rtd-theme python3-breathe python3-pip $ pip3 install --user exhale build docs: $ mkdir -p build-docs $ cd build-docs $ cmake .. && make doc serve built docs via http, will be served at http://127.0.0.1:8000/ $ python3 -m http.server -d docs/html ================================================ FILE: docs/exit-setup.md ================================================ to configure lokinet to be an exit add into `lokinet.ini`: [router] min-connections=8 max-connections=16 [network] exit=true keyfile=/var/lib/lokinet/exit.private reachable=1 ifaddr=10.0.0.1/16 hops=2 paths=8 post setup for exit (as root) given `eth0` is used to get to the internet: # echo 1 > /proc/sys/net/ipv4/ip_forward # iptables -t nat -A POSTROUTING -s 10.0.0.0/16 -o eth0 -j MASQUERADE ================================================ FILE: docs/fix-markdown.sh ================================================ #!/bin/bash # apply markdown file content quarks # rewrite br tags sed -i 's|
|
|g' $@ ================================================ FILE: docs/ideal-ux.md ================================================ # What does Lokinet actually do? Lokinet is an onion routed authenticated unicast IP network. It exposes an IP tunnel to the user and provides a dns resolver that maps `.loki` and `.snode` gtld onto a user defined ip range. Lokinet allows users to tunnel arbitrary ip ranges to go to a `.loki` address to act as a tunnel broker via another network accessible via another lokinet client. This is commonly known as an "exit node" but the way lokinet does this is much more generic so that term is not very accurate given what it actually does. The `.snode` gtld refers to a router on the network by its public ed25519 key. The `.loki` gtld refers to clients that publish the existence anonymously to the network by their ed25519 public key. (`.loki` also has the ability to use short names resolved via external consensus method, like a blockchain). # How do I use Lokinet? set system dns resolver to use the dns resolver provided by lokinet, make sure the upstream dns provider that lokinet uses for non lokinet gtlds is set as desired (see lokinet.ini `[dns]` section) configure exit traffic provider if you want to tunnel ip traffic via lokinet, by default this is off as we cannot provide a sane defualt that makes everyone happy. to enable an exit node, see lokinet.ini `[network]` section, add multiple `exit-node=exitaddrgoeshere.loki` lines for each endpoint you want to use for exit traffic. each `exit-node` entry will be used to randomly stripe across per IP you are sending to. note: per flow (ip+proto/port) isolation is trivial on a technical level but currently not implemented at this time. # Can I run lokinet on a soho router? Yes and that is the best way to run it in practice. ## The "easy" way We have a community maintained solution for ARM SBCs like rasperry pi: https://github.com/necro-nemesis/LabyrinthAP ## The "fun" way (DIY) It is quite nice to DIY. if you choose to do so there is some assembly required: on the lokinet side, make sure that the... * ip ranges for `.loki` and `.snode` are statically set (see lokinet.ini `[network]` section `ifaddr=` option) * network interace used by lokinet is statically set (see lokinet.ini `[network]` section `ifname=` option) * dns socket is bound to an address the soho router's dns resolver can talk to, see `[dns]` section `bind=` option) on the soho router side: * route queries for `.loki` and `.snode` gtld to go to lokinet dns on soho router's dns resolver * use dhcp options to set dns to use the soho router's dns resolver * make sure that the ip ranges for lokinet are reachable via the LAN interface * if you are tunneling over an exit ensure that LAN traffic will only forward to go over the lokinet vpn interface ================================================ FILE: docs/index.md.in ================================================ # Lokinet @lokinet_VERSION@ (git rev: @GIT_VERSION@) summary goes here ## Overview [code internals](index_namespaces.md) ================================================ FILE: docs/install.md ================================================ # Installing If you are simply looking to install Lokinet and don't want to compile it yourself we provide several options for platforms to run on: Tier 1: * [Linux](#linux-install) * [Windows](#windows-install) * [MacOS](#macos-install) Tier 2: * [FreeBSD](#freebsd-install) Currently Unsupported Platforms: (maintainers welcome) * [Android](#apk-install) * Apple iPhone * Homebrew * \[Insert Flavor of the Month windows package manager here\] ## Official Builds ### Windows / MacOS You can get the latest stable release for lokinet on windows or macos from https://lokinet.org/ or check the [releases page on github](https://github.com/oxen-io/lokinet/releases). ### Linux You do not have to build from source if you do not wish to, we provide [apt](#deb-install) and [rpm](#rpm-install) repos. #### APT Repository You can install debian packages from `deb.oxen.io` by adding the apt repo to your system. $ sudo curl -so /etc/apt/trusted.gpg.d/oxen.gpg https://deb.oxen.io/pub.gpg $ echo "deb https://deb.oxen.io $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/oxen.list This apt repo is also available via lokinet at `http://deb.loki` Once added you can install lokinet with: $ sudo apt update $ sudo apt install lokinet When running from debian package the following steps are not needed as it is already running and ready to use. You can stop/start/restart it using `systemctl start lokinet`, `systemctl stop lokinet`, etc. #### RPM We also provide an RPM repo, see `rpm.oxen.io`, also available on lokinet at `rpm.loki` ## Bleeding Edge dev builds automated builds from dev branches for the brave or impatient can be found from our CI pipeline [here](https://oxen.rocks/oxen-io/lokinet/). (warning: these nightly builds may or may not consume your first born child.) ## Building Build requirements: * Git * CMake * C++ 17 capable C++ compiler * libuv >= 1.27.0 * libsodium >= 1.0.18 * libssl (for lokinet-bootstrap) * libcurl (for lokinet-bootstrap) * libunbound * libzmq * cppzmq ### Linux Compile If you want to build from source: $ sudo apt install build-essential cmake git libcap-dev pkg-config automake libtool libuv1-dev libsodium-dev libzmq3-dev libcurl4-openssl-dev libevent-dev nettle-dev libunbound-dev libssl-dev nlohmann-json3-dev $ git clone --recursive https://github.com/oxen-io/lokinet $ cd lokinet $ mkdir build $ cd build $ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF $ make -j$(nproc) $ sudo make install set up the initial configs: $ lokinet -g $ lokinet-bootstrap after you create default config, run it: $ lokinet This requires the binary to have the proper capabilities which is usually set by `make install` on the binary. If you have errors regarding permissions to open a new interface this can be resolved using: $ sudo setcap cap_net_admin,cap_net_bind_service=+eip /usr/local/bin/lokinet #### Arch Linux Due to [circumstances beyond our control](https://github.com/oxen-io/lokinet/discussions/1823) a working `PKGBUILD` can be found [here](https://raw.githubusercontent.com/oxen-io/lokinet/makepkg/contrib/archlinux/PKGBUILD). #### Cross Compile For Linux current cross targets: * aarch64-linux-gnu * arm-linux-gnueabihf * mips-linux-gnu * mips64-linux-gnuabi64 * mipsel-linux-gnu * powerpc64le-linux-gnu install the toolchain (this one is for `aarch64-linux-gnu`, you can provide your own toolchain if you want) $ sudo apt install g{cc,++}-aarch64-linux-gnu build 1 or many cross targets: $ ./contrib/cross.sh arch_1 arch_2 ... arch_n ### Building For Windows windows builds are cross compiled from debian/ubuntu linux additional build requirements: * nsis * cpack * rsvg-convert (`librsvg2-bin` package on Debian/Ubuntu) setup: $ sudo apt install build-essential cmake git pkg-config mingw-w64 nsis cpack automake libtool $ sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix $ sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix building: $ git clone --recursive https://github.com/oxen-io/lokinet $ cd lokinet $ ./contrib/windows.sh ### Compiling for MacOS Source code compilation of Lokinet by end users is not supported or permitted by apple on their platforms, see [this](../contrib/macos/README.txt) for more information. If you find this disagreeable consider using a platform that permits compiling from source. ### FreeBSD Currently has no VPN Platform code, see issue `#1513` build: $ pkg install cmake git pkgconf $ git clone --recursive https://github.com/oxen-io/lokinet $ cd lokinet $ mkdir build $ cd build $ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON -DBUILD_STATIC_DEPS=ON .. $ make install (root): # make install ### Android We have an Android APK for lokinet VPN via android VPN API. Coming to F-Droid whenever that happens. [[issue]](https://github.com/oxen-io/lokinet-flutter-app/issues/8) * [source code](https://github.com/oxen-io/lokinet-flutter-app) ================================================ FILE: docs/liblokinet-dev-guide.md ================================================ # Embedding Lokinet into an existing application When all else fails and you want to deploy lokinet inside your app without the OS caring you can embed an entire lokinet client and a few of the upper network layers into your application manually. ## Why you should avoid this route `// TODO: this` ## When you cannot avoid this route, how do i use it? `// TODO: this` ================================================ FILE: docs/macos-signing.txt ================================================ If you are reading this to try to build Lokinet for yourself for an Apple operating system and simultaneously care about open source, privacy, or freedom then you, my friend, are a walking contradiction: you are trying to get Lokinet to work on a platform that actively despises open source, privacy, and freedom. Even Windows is a better choice in all of these categories than Apple. This directory contains the magical incantations and random voodoo symbols needed to coax an Apple build. There's no reason builds have to be this stupid, except that Apple wants to funnel everyone into the no-CI, no-help, undocumented, non-toy-apps-need-not-apply modern Apple culture. This is disgusting. But it gets worse. The following two files, in particular, are the very worst manifestations of this already toxic Apple cancer: they are required for proper permissions to run on macOS, are undocumented, and can only be regenerated through the entirely closed source Apple Developer backend, for which you have to pay money first to get a team account (a personal account will not work), and they lock the resulting binaries to only run on individually selected Apple computers selected at the time the profile is provisioned (with no ability to allow it to run anywhere). lokinet.dev.provisionprofile lokinet-extension.dev.provisionprofile This is actively hostile to open source development, but that is nothing new for Apple. There are also release provisioning profiles lokinet.release.provisionprofile lokinet-extension.release.provisionprofile These ones allow distribution of the app, but only if notarized, and again require notarization plus signing by a (paid) Apple developer account. In order to make things work, you'll have to replace these provisioning profiles with your own (after paying Apple for the privilege of developing on their platform, of course) and change all the team/application/bundle IDs to reference your own team, matching the provisioning profiles. The dev provisioning profiles must be a "macOS Development" provisioning profile, and must include the signing keys and the authorized devices on which you want to run it. (The profiles bundled in this repository contains the lokinet team's "Apple Development" keys associated with the Oxen project, and mac dev boxes. This is *useless* for anyone else). For release builds, you still need a provisioning profile, but it must be a "Distribution: Developer ID" provisioning profile, and are tied to a (paid) Developer ID. The ones in the repository are attached to the Oxen Project Developer ID and are useless to anyone else. Once you have that in place, you need to build and sign the package using a certificate matching your provisioning profile before your Apple system will allow it to run. (That's right, your $2000 box won't let you run programs you build from source on it unless you also subscribe to a $100/year Apple developer account). Okay, so now that you have paid Apple more money for the privilege of using your own computer, here's how you make a signed lokinet app: 1) Decide which type of build you are doing: a lokinet system extension, or an app extension. The former must be signed and notarized and will only work when placed in the /Applications folder, but will not work as a dev build and cannot be distributed outside the Mac App Store. The latter is usable as a dev build, but still requires a signature and Apple-provided provisioningprofile listing the limited number of devices on which it is allowed to run. For system extension builds you want to add the -DMACOS_SYSTEM_EXTENSION=ON flag to cmake. 2) Figure out the certificate to use for signing and make sure you have it installed. For a distributable system extension build you need a "Developer ID Application" key and certificate, issued by your paid developer.apple.com account. For dev builds you need a "Apple Development" certificate. In most cases you don't need to specify these; the default cmake script will figure them out. (If it can't, e.g. because you have multiple of the right type installed, it will error with the keys it found). To be explicit, use `security find-identity -v` to list your keys, then list the key identity with -DCODESIGN_ID=..... 3) If you are doing a system extension build you will need to provide notarization login information by adding: -DMACOS_NOTARIZE_ASC=XYZ123 -DMACOS_NOTARIZE_USER=me@example.com -DMACOS_NOTARIZE_PASS=@keychain:codesigning-password a) The first value (XYZ123) needs to be the organization-specific unique value, and is printed in brackets in the certificate description. For example: 15095CD1E6AF441ABC69BDC52EE186A18200A49F "Developer ID Application: Some Developer (ABC123XYZ9)" would require ABC123XYZ9 for this field. b) The USER field is your Apple Developer login e-mail address. c) The PASS field is a keychain reference holding your "Application-Specific Password". To set up such a password for your account, consult Apple documentation. Once you have it, load it into your keychain via: export HISTFILE='' # Don't want to store this in the shell history xcrun altool --store-password-in-keychain-item "codesigning-password" -u "user" -p "password" You can change "codesigning-password" to whatever you want (just make sure it agrees with the -DMACOS_NOTARIZE_PASS option you build with). "user" and "password" should be your developer account device-specific login credentials provided by Apple. To make your life easier, stash these settings into a `~/.notarization.cmake` file inside your home directory; if you have not specified them in the build, and this file exists, lokinet's cmake will load it: set(MACOS_NOTARIZE_USER "me@example.com") set(MACOS_NOTARIZE_PASS "@keychain:codesigning-password") set(MACOS_NOTARIZE_ASC "ABC123XYZ9") 4) Build and sign the package; there is a script `contrib/mac.sh` that can help (extra cmake options you need can be appended to the end), or you can build yourself in a build directory. See the script for the other cmake options that are typically needed. Note that `-G Ninja` (as well as a working ninja builder) are required. If you get an error `errSecInternalComponent` this is Apple's highly descriptive way of telling you that you need to unlock your keychain, which you can do by running `security unlock`. If doing it yourself, `ninja sign` will build and then sign the app. If you need to also notarize (e.g. for a system extension build) run `./notarize.py` from the build directory (or alternatively `ninja notarize`, but the former gives you status output while it runs). 5) Packaging the app: you want to use `-DLOKINET_PACKAGE=ON` when configuring with cmake and then, once all signing and notarization is complete, run `cpack` which will give you a .dmg and a .zip containing the release. ================================================ FILE: docs/mkdocs.yml ================================================ site_name: Lokinet theme: name: 'readthedocs' docs_dir: markdown site_dir: html ================================================ FILE: docs/net-comparisons.md ================================================ # How is lokinet different than ... ## Tor Browser Tor browser is a hardened Firefox Web Browser meant exclusively to surf http(s) sites via Tor. It is meant to be a complete self contained browser you open and run to surf the Web (not the internet) anonymously. Lokinet does not provide a web browser at this time because that is not a small task to do at all, and an even larger task to do in a way that is secure, robust and private. Community Contribuitions Welcomed. ## Tor/Onion Services While Tor Browser is the main user facing product made by Tor Project, the main powerhouse is Tor itself. Tor provides a way to anonymize tcp connections made by an initiator and optionally additionally the recipient, when using a .onion address. Lokinet provides a similar feature set but can carry anything that can be encapsulated in an IP packet (currently only unicast traffic). The Lokinet UX differs greatly from that of Tor. By default we do not provide exit connectivity. Because each user's threat model greatly varies in scope and breadth, there exists no one size fits all way to do exit connectivity. Users obtain their exit node information out-of-band at the moment. In the future we want to add decentralized network wide service discovery not limited to just exit providers, but this is currently unimplemented. We think that by being hands-off with respect to exit node requirements a far more diverse set of exit nodes can exist. In addition to having totally open unrestrcited exits, there is merit to permitting "specialized" exit providers that are allowed to do excessive filtering or geo blocking for security theatre checkbox compliance. Lokinet additionally encourages the manual selection and pinning of edge connections to fit each user's threat model. ## I2P Integrating applications to utilize i2p's network layer is painful and greatly stunts mainstream adoption. Lokinet takes the inverse approach of i2p: make app integration in lokinet should require zero custom shims or modifications to code to make it work. ## DVPNs / Commercial VPN Proxies One Hop VPNs can see your real IP and all of the traffic you tunnel over them. They are able to turn your data over to authorities even if they claim to not log. Lokinet can see only 1 of these 2 things, but NEVER both: * Encrypted data coming from your real IP going to the first hop Lokinet Router forwarded to another Lokinet Router. * A lokinet exit sees traffic coming from a `.loki` address but has no idea what the real IP is for it. One Hop Commericial VPN Tunnels are no log by **policy**. You just have to trust that they are telling the truth. Lokinet is no log by **design** it doesn't have a choice in this matter because the technology greatly hampers efforts to do so. Any Lokinet Client can be an exit if they want to and it requires no service node stakes. Exits are able to charge users for exiting to the internet, tooling for orechestrating this is in development. ================================================ FILE: docs/project-structure.md ================================================ # Lokinet Project Structure this codebase is a bit large. this is a high level map of the current code structure. ## Lokinet executable main functions `(/daemon)` * `lokinet.cpp`: lokinet daemon executable * `lokinet.swift`: macos sysex/appex executable * `lokinet-vpn.cpp`: lokinet rpc tool for controlling exit node usage * `lokinet-bootstrap.cpp`: legacy util for windows, downloads a bootstrap file via https ## Lokinet public headers `(/include)` `lokinet.h and lokinet/*.h`: C headers for embedded lokinet `llarp.hpp`: semi-internal C++ header for lokinet executables ## Lokinet core library `(/llarp)` * `/llarp`: contains a few straggling compilation units * `/llarp/android`: android platform compat shims * `/llarp/apple`: all apple platform specific code * `/llarp/config`: configuration structs, generation/parsing/validating of config files * `/llarp/consensus`: network consenus and inter relay testing * `/llarp/constants`: contains all compile time constants * `/llarp/crypto`: cryptography interface and implementation, includes various secure helpers * `/llarp/dht`: dht message structs, parsing, validation and handlers of dht related parts of the protocol * `/llarp/dns`: dns subsytem, dns udp wire parsers, resolver, server, rewriter/interceptor, the works * `/llarp/ev`: event loop interfaces and implementations * `/llarp/exit`: `.snode` endpoint "backend" * `/llarp/handlers`: packet endpoint "frontends" * `/llarp/iwp`: "internet wire protocol", hacky homegrown durable udp wire protocol used in lokinet * `/llarp/link`: linklayer (node to node) communcation subsystem * `/llarp/messages`: linklayer message parsing and handling * `/llarp/net`: wrappers and helpers for ip addresses / ip ranges / sockaddrs, hides platform specific implemenation details * `/llarp/path`: onion routing path logic, both client and relay side, path selection algorithms. * `/llarp/peerstats`: deprecated * `/llarp/quic`: plainquic shims for quic protocol inside lokinet * `/llarp/router`: the relm of the god objects * `/llarp/routing`: routing messages (onion routed messages sent over paths), parsing, validation and handler interfaces. * `/llarp/rpc`: lokinet zmq rpc server and zmq client for externalizing logic (like with blockchain state and custom `.loki` endpoint orchestration) * `/llarp/service`: `.loki` endpoint "backend" * `/llarp/simulation`: network simulation shims * `/llarp/tooling`: network simulation tooling * `/llarp/util`: utility function dumping ground * `/llarp/vpn`: vpn tunnel implemenation for each supported platform * `/llarp/win32`: windows specific code ## Component relations ### `/llarp/service` / `/llarp/handlers` / `/llarp/exit` for all codepaths for traffic over lokinet, there is 2 parts, the "frontend" and the "backend". the "backend" is responsible for sending and recieving data inside lokinet using our internal formats via paths, it handles flow management, lookups, timeouts, handover, and all state we have inside lokinet. the "fontend", is a translation layer that takes in IP Packets from the OS, and send it to the backend to go where ever it wants to go, and recieves data from the "backend" and sends it to the OS as an IP Packet. there are 2 'backends': `.snode` and `.loki` there are 2 'frontends': "tun" (generic OS vpn interface) and "null" (does nothing) * `//TODO: the backends need to be split up into multiple sub components as they are a kitchen sink.` * `//TODO: the frontends blend into the backend too much and need to have their boundery clearer.` ### `/llarp/ev` / `/llarp/net` / `/llarp/vpn` these contain most of the os/platform specific bits * `//TODO: untangle these` ### `/llarp/link` / `/llarp/iwp` node to node traffic logic and wire protocol dialects * `//TODO: make better definitions of interfaces` * `//TODO: separte implementation details from interfaces` ## Platform contrib code `(/contrib)` grab bag directory for non core related platform specific non source code * `/contrib/format.sh`: clang-format / jsonnetfmt / swiftformat helper, will check or correct code style. system layer and packaging related: * `/contrib/NetworkManager` * `/contrib/apparmor` * `/contrib/systemd-resolved` * `/contrib/lokinet-resolvconf` * `/contrib/bootstrap` build shims / ci helpers * `/contrib/ci` * `/contrib/patches` * `/contrib/cross` * `/contrib/android.sh` * `/contrib/android-configure.sh` * `/contrib/windows.sh` * `/contrib/windows-configure.sh` * `/contrib/mac.sh` * `/contrib/ios.sh` * `/contrib/cross.sh` ================================================ FILE: docs/readme.md ================================================ # Lokinet Docs This is where Lokinet documentation lives. ## Contents: ### Local Environment Set-Up - [Installing Lokinet](install.md) - [Using Lokinet](ideal-ux.md) ### High Level Overview - [Lokinet versus \[insert network technology name here\]](net-comparisons.md) - [Lokinet architecture](architecture.md) - [Lokinet and DNS](dns-overview.md) - [Limitations of Lokinet](we-cannot-make-sandwiches.md) ### Lokinet Internals - [Git repo layout and project structure](project-structure.md) - [Building Doxygen Docs for internals](doxygen.md) ### Lokinet (SN)Application Developer Portal - [SNapps development overview](snapps-dev-guide.md) - [Embedded Lokinet](liblokinet-dev-guide.md) ================================================ FILE: docs/refactor_notes.md ================================================ # High Level Iterative Approach the desired outcome of this refactor will be splitting the existing code up into a stack of new components. a layer hides all functionality of the layer below it to reduce the complexity like the OSI stack intends to. the refactor starts at the top layer, wiring up the old implementation piecewise to the top layer. once the top layer is wired up to the old implementation we will move down to the next layer. this will repeat until we reach the bottom layer. once the old implementation is wired up into these new clearly defined layers, we can fixup or replace different parts of each layer one at a time as needed. working down from each layer will let us pick apart the old implementation (if needed) that we would wire up to the new base classes for that layer we are defining now without worrying about what is below it (yet). this refactor is very able to be split up into small work units that (ideally) do not confict with each other. PDU: https://en.wikipedia.org/wiki/Protocol_data_unit # The New Layers from top to bottom the new layers are: * Platform Layer * Flow Layer * Routing Layer * Onion Layer * Link Layer * Wire Layer ## Platform Layer this is the top layer, it is responsibile ONLY to act as a handler of reading data from the "user" (via tun interface or whatever) to forward to the flow layer as desired, and to take data from the flow layer and send it to the "user". any kind of IP/dns mapping or traffic isolation details are done here. embedded lokinet would be implemented in this layer as well, as it is without a full tun interface. Platform layer PDU are what the OS gives us and we internally convert them into flow layer PDU and hand them off to the flow layer. ## Flow Layer this layer is tl;dr mean to multiplex data from the platform layer across the routing layer and propagating PDU from the routing to the platform layer if needed. the flow layer is responsible for sending platform layer PDU across path we have already established. this layer is informed by the routing layer below it of state changes in what paths are available for use. the flow layer requests from the layer below to make new paths if it wishes to get new ones on demand. this layer will recieve routing layer PDU from the routing layer and apply any congestion control needed to buffer things to the os if it is needed at all. flow layer PDU are (data, ethertype, src-pubkey, dst-pubkey, isolation-metric) tuples. data is the datum we are tunneling over lokinet. ethertype tells us what kind of datum this is, e.g. plainquic/ipv4/ipv6/auth/etc. src-pubkey and dst-pubkey are public the ed25519 public keys of each end of the flow in use. the isolation metric is a piece of metadata we use to distinguish unique flows (convotag). in this new seperation convotags explicitly do not hand over across paths. ## Routing Layer this layer is tl;dr meant for path management but not path building. the routing layer is responsible for sending/recieving flow layer PDU, DHT requests/responses, latency testing PDU and any other kind of PDU we send/recieve over the onion layer. this layer will be responsible for managing paths we have already built across lokinet. the routing layer will periodically measure path status/latency, and do any other kinds of perioidic path related tasks post build. this layer when asked for a new path from the flow layer will use one that has been prebuilt already and if the number of prebuilt paths is below a threshold we will tell the onion layer to build more paths. the routing layer will recieve path build results be their success/fail/timeout from the onion layer that were requested and apply any congestion control needed at the pivot router. routing layer PDU are (data, src-path, dst-path) tuples. data is the datum we are transferring between paths. src-path and dst-path are (pathid, router id) tuples, the source being which path this routing layer PDU originated from, destination being which path it is going to. in the old model, router id is always the router that recieves it as the pivot router, this remains the same unless we explicitly provide router-id. this lets us propagate hints to DHT related PDU held inside the datum. ## Onion Layer the onion layer is repsonsible for path builds, path selection logic and low level details of encrypted/decrypting PDU that are onion routed over paths. this layer is requested by the routing layer to build a path to a pivot router with an optional additional constraints (e.g. unique cidr/operator/geoip/etc, latency constaints, hop length, path lifetime). the onion layer will encrypt PDU and send them to link layer as (frame/edge router id) tuples, and recieve link layer frames from edge routers, decrypt them and propagate them as needed to the routing layer. this layer also handles transit onion traffic and transit path build responsibilities as a snode and apply congestion control as needed per transit path. the onion layer PDU are (data, src-path, dst-path) tuples. src-path and dst-path are (router-id, path-id) tuples which contain the ed25519 pubkey of the node and the 128 bit path-id it was associated with. data is some datum we are onion routing that we would apply symettric encryption as needed before propagating to upper or lower layers. ## Link Layer the link layer is responsbile for transmission of frames between nodes. this layer will handle queuing and congestion control between wire proto sessions between nodes. the link layer is will initate and recieve wire session to/from remote nodes. the link layer PDU is (data, src-router-id, dst-router-id) tuples. data is a datum of a link layer frame. src-router-id and dst-router-id are (ed25519-pubkey, net-addr, wire-proto-info) tuples. the ed25519 pubkey is a .snode address, (clients have these too but they are ephemeral). net-addr is an (ip, port) tuple the node is reachable via the wire protocol. wire-proto-info is dialect specific wire protocol specific info. ## Wire Layer the wire layer is responsible for transmitting link layer frames between nodes. all details here are specific to each wire proto dialect. ================================================ FILE: docs/snapps-dev-guide.md ================================================ # (SN)Apps Development Guide ## Our approach `// TODO: this` ## Differences From Other approaches `// TODO: this` ### SOCKS Proxy/HTTP Tunneling `// TODO: this` ### Embedding a network stack `// TODO: this` ## High Level Code Practices `// TODO: this` ### Goodisms `// TODO: this` ### Badisms `// TODO: this` ================================================ FILE: docs/spanish/LICENSE ================================================ Especificaciones del Protocolo de Enrutado Anónimo de Baja Latencia - Low Latency Anonymous Routing Protocol Escrito en el 2017 por Jeff Becker En el alcance de lo posible dentro de la ley, el (los) autor(es) dedica(ron) al dominio público mundial todo el derecho de copia y los derechos relacionados y semejantes relacionados a esta especificación del protocolo. Este software se distribuido sin garantia alguna. Usted deberia tener una copia de la Dedicación de Dominio Público CC0 junto con este software. En caso contrario, vea . ================================================ FILE: docs/spanish/README ================================================ Carpeta de las Especificaciones del Protocolo Todo los documentos de esta carpeta están licenciados como CC0 y son del dominio público. Por favor tome nota que la implementación de referencia de LokiNET está licenciada bajo la licencia ZLIB. ================================================ FILE: docs/spanish/vision-general.txt ================================================ LLARP - Low Latency Anon Routing Protocol - Protocolo de Enrutado Anónimo de Baja Latencia Resumen TL;DR: un router onion con una interfaz tun para transportar paquetes ip de forma anónima entre usted y la internet, y desde dentro de ella hacia otros usuarios. Este documento describe la visión general de LLARP, detalla todas las metas del proyecto, lo que no son metas y la arquitectura de la red desde una perspectiva general. Prefacio Trabajar en I2P ha sido experiencia realmente grande para todo el que se involucra. Después bastante deliberación, yo decidí empezar a construir un protocolo onion de "proxima generacion". LLARP es específicamente (en la actualidad) un proyecto de investigación para explorar la siguiente cuestión: "¿Que hubiera sido si I2P fuera construido en el año presente (2018)? ¿Que hubiera sido diferente?" Lo que No son Metas del Proyecto: Este proyecto no intenta resolver la correlación por la forma del tráfico o ataques a la red patrocinadas por el estado. Lo primero es una propiedad inherente de las redes computacionales de baja latencia que yo personalmente no pienso que es posible completamente "resolver" de forma apropiada. Lo segundo es una amenaza que por el momento se encuentra fuera de los alcances de las herramientas actuales que me están disponibles. Este proyecto no pretende ser la aplicación mágica de nivel curalotodo para la aplicación o la seguridad del usuario final. Después de todo, eso es un problema que existe entre la silla y el teclado. La Única Meta del Proyecto: LLARP en una suite de protocolos que pretende mantener anónima la IP mediante el ofrecimiento de un agente de túnel anónimo a nivel red (IPv4/IPv6) tanto para "servicios ocultos" y la comunicación de regreso a la "red transparente" (la Internet comun). Tanto las comunicación del servicio oculto y la red transparente DEBEN permitir tanto el trafico de salida y el tráfico de entrada a nivel red sin implementar NAT alguna (con excepción de la IPv4 de la cual la NAT es permitida debido a la escasez de direcciones). En concreto, Queremos permitir tanto la salida y la entrada anonima del trafico a nivel red entre las redes habilitadas por LLARP y la Internet. Las razones de porque empezar desde cero: A pesar de los mejores esfuerzos del Proyecto Tor para popularizar el uso de Tor, Tor2Web parece ser ampliamente popular para las personas que no desean optar estar dentro del ecosistema. Mi solucion propuesta seria permitir el tráfico de entrada desde los "nodos de salida" en adición de permitir el tráfico de salida. No tengo idea en cómo pudiera hacer esto los protocolos actuales en Tor, o si es posible o recomendable intentar tal cosa ya que no estoy familiarizado con su ecosistema. I2P se hubiera usado como un medio para transito anonimo IP cifrado pero la red actual tiene problemas con la latencia y el rendimiento. Avanzar I2P sobre criptografía moderna está en proceso dentro de I2P, proceso que ya lleva por lo menos 5 años y con menos progreso que lo deseado. Así como algunos antes de mi, yo he llegado a la conclusión que seria mas rapido rehacer todo el stack "de la forma correcta" que estar esperando a que I2P termine sus avances. Dicho esto, nada está previniendo a I2P en ser usado para el transito de trafico anonimo IP cifrado dentro de un futuro en que I2P termine sus migraciones de protocolo, pero yo no quiero esperarlo. En concreto, yo quiero tomar las "mejores partes" de Tor e I2P, y hacer una nueva suite de protocolos. Para ambos, tanto Tor e I2P, les tengo 2 categorías de sus atributos. lo bueno lo malo y lo feo Lo bueno (I2P): I2P apunta a proveer una capa de red anónima de carga balanceada insuplantable. Yo quiero esta característica I2P tiene agilidad de confianza, no necesita tener alguna confianza integrada en el código dentro de su arquitectura de red. Incluso la fase de arranque de la red puede realizarse desde un solo router, si el usuario lo desea (aunque esto esté desaconsejado) Yo quiero esta característica Lo bueno (Tor): Tor abarca la realidad de la actual infraestructura de la Internet al tener una arquitectura cliente/servidor. Esto permite tener barreras de acceso muy bajas para usar la red Tor, y barreras más altas de acceso para contribuir a la infraestructura de enrutado. Esto promueve una forma saludable de red con servidores de alta capacidad ofreciendo servicios a clientes de baja capacidad que "se cuelgan del extremo" de la red. Yo quiero esta característica Lo malo y lo feo (I2P): Malo: I2P usa criptografia vieja, en especial ElGamal de 2048 bits usando primos no estandares. El uso de ElGamal es tan constante a traves del stack del protocolo I2P que existe en cada nivel suyo. Removerlo es una tarea masiva que está tomando mucho MUCHO tiempo. Yo no quiero esta característica Feo: I2P no puede actualmente mitigar la mayoría de los ataques Sybil, con su actual arquitectura de red. I2P recientemente añadió algunas soluciones de listas de bloqueo que están firmadas por los firmantes de lanzamiento, pero esto es probable que no escale en el evento de una ataque "grande". Además I2P tampoco tiene el personal para ese tipo de ataques. Este es un problema difícil de resolver en que la red Loki pudiera ayudar con la creación de una barrera financiera para correr múltiples relays. Lo malo y lo feo (Tor): Malo: Tor está estrictamente orientado al TCP. Yo no quiero esta característica ================================================ FILE: docs/tcp-over-quic.md ================================================ # "liblokinet" TCP-over-QUIC In order for lokinet to work in an embedded version (which I will call "liblokinet" in this document), which lokinet cannot create TUN device (either because the host OS doesn't support them, or because lokinet needs to run without permissions to manage them) lokinet needs a solution for sending TCP data from the device to a remote lokinet client (i.e. a snapp, a snode, or another liblokinet client). Since the vast majority of network connectivity relies on TCP stream connections, not supporting them would be a severe limitation of a lokinet library that would make it nearly useless. Traditional "full" lokinet does not need to solve this problem: it creates virtual IPs on the TUN interface that map to every looked-up `.loki` address and then the host system's in-kernel TCP layer handles the intricacies of TCP including acknowledgement, retry, and so on. While there are user-space TCP implementations available, they are generally incomplete, unmaintained, or both, which would mean substantial work and ongoing maintenance for us to adopt or reimplement such a user-space TCP layer, for which we would most likely be the only user and contributor. Instead this proposal is for lokinet to support a tunneled TCP stream mode where TCP traffic is carried over lokinet via a subset of the [QUIC](https://datatracker.ietf.org/doc/draft-ietf-quic-transport/) protocol. Unlike TCP, QUIC has several well-maintained user-space implementations which allow us to use, rather than create, a well-maintained QUIC implementation. ## Overview The high-level strategy of how we handle such a stream connection is to have TCP connections established only within the local device. A liblokinet application would invoke a lokinet call to establish such a connection to proxy to a remote host by lokinet name and TCP port. This would first establish a lokinet connection to the remote host, then open a QUIC connection over it and start listening for TCP connections on a local port. When a new TCP connection is established on this port lokinet will establish a new QUIC stream over the existing connection, specifying the destination port while initializing the stream. (The client is free to establish as many TCP connections as it wants: each one becomes a separate QUIC stream). The situation is similar for the receiving lokinet client: it would listen for incoming QUIC connections on the local lokinet IP and, when establishing a QUIC stream, would establish a local TCP connection to the requested port on the lokinet IP. Any incoming stream data is then forwarded into this TCP connection, and any responses are sent back via the QUIC stream. ## Example For example, suppose `snap7.loki` is a lokinet snapp with a web server listening on port 80 and a liblokinet client `omg42.loki` wants to connect to it to retrieve a cat photo. With a full lokinet client, the DNS request for `omg56789.loki` triggers creation of a virtual IP on the TUN device, returns the IP to the system, and any TCP packets sent to this IP are forwarded to the primary lokinet IP of `azfoj123.loki`, where an HTTP server is ready and waiting to provide cat photos. With a liblokinet client, this process will looks a little different: the client will first make a call to the liblokinet library (rather than a DNS request) specifying the lokinet host name and TCP port it wants to connect to (note that this is pseudo-code; the actual implementation calls will have to deal with various details such as connection delays and timeouts that are omitted here): result = lokinet_stream_connect(lokinet_addr, port) if result->connection_established: http_get("http://" + result->local_address + ":" + result->local_port + "/cat.jpg") Here `http_get` would need no knowledge of lokinet at all: it will simply connect via TCP to an address such as `127.0.0.1:4716` for the HTTP request. It will send the request, and receive it, over this localhost TCP socket. Internally, lokinet will have established a QUIC connection to the remote host, and started listening for TCP connections on the localhost port. When `http_get` establishes a TCP connection on this local port it will create a QUIC stream on the established QUIC connection and forward all stream data received from the TCP connection into the QUIC stream, and any data that comes back over the QUIC stream will similarly be copied into the localhost TCP connection. Effectively the data path of data send from the app on omg42.loki to the HTTP snapp on omg42.loki looks like this: ┌omg42.loki────────────┐ ┌snap7.loki───────────┐ │ Main app thread │ │ HTTP │ │ TCP localhost:4567 ─>│─┐ │ TCP 172.16.0.1:80 <─│─┐ ├──────────────────────┤ │ ╞═════════════════════╡ │ │ liblokinet (in app) │ │ │ lokinet (on host) │ │ │ TCP localhost:4567 <─│─┘ ┌>│─> QUIC UDP │ │ │ QUIC UDP ─>│───... Lokinet routers ...─┘ │ TCP 172.16.0.1:80 ─>│─┘ └──────────────────────┘ └─────────────────────┘ (These connections are all bi-direction, so any TCP stream data replied from omg42.loki follows the same path in reverse.) ## Implementation details/notes Implementation library: `ngtcp2` is a robust, maintained library that fits our needs well. ### Not a general QUIC server/client The QUIC tunnel described here is *only* for Lokinet TCP streams; it is not intended to be interoperable with general QUIC clients, which allows us some leeway to not support some aspects of QUIC that are of no advantage over a lokinet conversation. ### No encryption Since lokinet traffic is itself encrypted and private, the built-in TLS layers of QUIC are something that we don't need or want. Thus the QUIC implementation used will simply use no-op encryption to pass data and avoid/ignore certificates. (`ngtcp2`, in particular, allows pluggable authentication to allow this). ### No address verification QUIC recommends address verification (among other things, to avoid amplification attacks). Lokinet connections already provide this and so we can safely not use it. ### Stream establishing Establishing a QUIC stream requires sending additional information during connection: namely the target connection port. Thus establishing a new stream will require some additional data to be passed, likely as the initial stream data. (Specification of how this data is to be encoded is not yet specified). ### Incoming TCP-over-QUIC connections Handling of incoming connections to a liblokinet client will require a similar process, but in reverse: - the client starts listening on a localhost TCP port - the client makes a call to liblokinet to inform it of this available listening port - incoming QUIC tunneled streams attempting to connect to that registered port are accepted and establish a new TCP connection as long as the stream stays open; data is forwarded between the two connections. - Should the client require end-point verification liblokinet will provide a function that can look up the remote lokinet address based on the source port of the TCP connection. (This is different from but analogous to a snapp doing a reverse DNS lookup on the source address to determine the remote address). Note: to be externally reachable by other lokinet clients, a liblokinet client would have to publish an introset; this introset would also include an additional flag indicating that TCP connections must be tunneled through a TCP-over-QUIC connection. Note 2: we additionally may want to signal during connection that new TCP connections back to us should be done over a QUIC tunnel, which requires also adding a flag when establishing the lokinet conversation. ### Non-tunneled incoming TCP connections Without a controllable TCP stack we have no ability to accept these, however since the introset (and conversation initiation) indicates that TCP should be tunneled, we should just drop these packets. ## Lokinet implementation notes ### Outbound connections -- liblokinet The application makes a liblokinet library call such as lokinet_stream_result res; lokinet_outbound_stream(&res, "some-snapp.loki", 2345); This initiates an outbound connection to the given lokinet remote, asking to connect to port 2345 on the remote. Plainquic begins listening on a random localhost port, and returns this via an entry in `res`. New connections establishes to this localhost port initiate new streams on the quic connection which are tunneled to the remote end. ### Inbound connections -- liblokinet The application needs to start listening on one or more TCP ports (e.g. on localhost, but doesn't have to be) and then registers a callback with lokinet about the availability of this port for incoming plainquic connections by setting up a callback: ```C int accept_inbound(const char *lokinet_addr, uint16_t port, sockaddr *addr, void *context) { // lokinet_addr is the remote lokinet client trying to establish a stream // port is the port they are trying to reach // If the client is allowed then set the local TCP socket address that the tunnel should // connect to in `addr` (which is big enough to allow either sockaddr_in or sockaddr_in6) sockaddr_in* a = (sockaddr_in*)addr; a->sin_family = AF_INET; a->sin_addr = INADDR_LOOPBACK; a->sin_port = htons(5678); // NB: Doesn't have to be the passed-in `port` return 0; // If this callback doesn't handle the requested port (will try other callbacks): return -1; // If this callback does handle it and the connection should be refused: return -2; // (Return values other than 0/-1/-2 are reserved and should not be used). } lokinet_inbound_stream(&accept_inbound, NULL /*context*/); ``` or, for the very simple case where connections should be available on some localhost port: ```C // All incoming tunneled connections for port 5678 should go to localhost:5678 lokinet_inbound_stream_simple(5678); ``` When a new plainquic connection arrives, if such a callback has been registered it will be called to determine whether the connection should be accepted and, if it is, where streams opened on that connection should be sent. (For the simple version, all inbound connections on port 5678 would be accepted and would be forwarded to localhost:5678; inbound connections for other ports would be refused). Each new plainquic stream initiated by the remote connection then establishes a new TCP connection to the IP/port set by the callback. ### Outbound connections - full lokinet When attempting to connect to a client who has indicated in its introset that it requires plainquic connections then plainquic will bind to and listen on the virtual (tun) TCP/IP port and establish a plainquic connection to the remote liblokinet on the given port. (Future connections to this port will establish new streams on the existing connection). Setting up the initial listener involves intercepting the initial TCP connection attempt (i.e. the SYN packet), starting to listen on it while simultaneously initiating the plainquic connection over lokinet. It may work sufficiently well (investigation required) to simply drop this initial SYN packet and let the initiator retry in a few moments to attack to the new listener which now goes into the plainquic listener which establishes a new stream. Thereafter the local application simply talks to this local listener and all stream data gets tunneled over lokinet to the remote liblokinet. ### Inbound connections - full lokinet This is fairly simple: when incoming quic-tunneled packets arrive we start up a plainquic server (if not already running), deliver the packets into it, and it tunnels incoming stream data into TCP connections to the primary lokinet IP (using the IP mapped to the lokinet endpoint as the source address). TODO: - Add quic protocol type to llarp/service/protocol_types.hpp - Convert stuff in plainquic code to use lokinet structures (e.g. logging, address encapsulation) - Add handler for QUIC packets to llarp/handlers/tun.cpp that see that protocol type and forward the packet off to the quic server to handle. - Get at the uvw event loop from the quic code so that we can put the plainquic stuff onto it rather than spinning up its own event loop. I was thinking about something like: `virtual std::shared_ptr get_uvw_loop() { return nullptr; }` in ev.h, and an override that returns the uvw event loop in the ev_libuv.h subclass (the type erasure through the shared_ptr means ev.h doesn't have to depend on any uvw.h headers). Then the quic code can just do something like: auto uv_loop = std::static_pointer_cast(ev->get_uvw_loop()); if (not uv_loop) { die("horribly"); } - convert the crap in the `main` functions copied from plainquic test code to exposed library calls. - decide whether we start up a quic server and/or client on demand, or just always start it. Outgoing conns: - Add "supported protocols" item to introset and (for liblokinet) leave off IPv4/v6 flags, but add quic protocol flag. ================================================ FILE: docs/we-cannot-make-sandwiches.md ================================================ # What Lokinet can't do Lokinet does a few things very well, but obviously can't do everything. ## Anonymize OS/Application Fingerprints Mitigating OS/Application Fingerprinting is the responsibility of the OS and Applications. Lokinet is an Unspoofable IP Packet Onion router, tuning OS fingerprints to be uniform would be a great thing to have in general even outside of the context of Lokinet. The creation of such an OS bundle is a great idea, but outside the scope of Lokinet. We welcome others to develop a solution for this. ## Malware Lokinet cannot prevent running of malicious programs. Computer security unfortunately cannot be solved unilaterally by networking software without simply dropping all incoming and outgoing traffic. ## Phoning Home Lokinet cannot prevent software which sends arbitrary usage data or private information to Microsoft/Apple/Google/Amazon/Facebook/etc. If you are using a service that requires the ability to phone home in order to work, that is a price you pay to use that service. ## Make Sandwiches At its core, Lokinet is technology that anonymizes and authenticates IP traffic. At this current time Lokinet cannot make you a sandwich. No, not even as root. ================================================ FILE: external/CMakeLists.txt ================================================ option(SUBMODULE_CHECK "Enables checking that vendored library submodules are up to date" ON) if(SUBMODULE_CHECK) find_package(Git) if(GIT_FOUND) function(check_submodule relative_path) execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE localHead) execute_process(COMMAND git rev-parse "HEAD:external/${relative_path}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE checkedHead) string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) if (upToDate) message(STATUS "Submodule 'external/${relative_path}' is up-to-date") else() message(FATAL_ERROR "Submodule 'external/${relative_path}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") endif() # Extra arguments check nested submodules foreach(submod ${ARGN}) execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path}/${submod} OUTPUT_VARIABLE localHead) execute_process(COMMAND git rev-parse "HEAD:${submod}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE checkedHead) string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) if (NOT upToDate) message(FATAL_ERROR "Nested submodule '${relative_path}/${submod}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") endif() endforeach() endfunction () message(STATUS "Checking submodules") check_submodule(CLI11) check_submodule(nlohmann) check_submodule(oxen-libquic) check_submodule(oxen-mq) check_submodule(pybind11) check_submodule(sqlite_orm) endif() endif() macro(system_or_submodule BIGNAME smallname target pkgconf subdir) if(NOT TARGET ${target}) option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES) pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET) endif() if(${BIGNAME}_FOUND) add_library(${smallname} INTERFACE) if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) else() target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) endif() message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") else() message(STATUS "using ${smallname} submodule ${subdir}") add_subdirectory(${subdir} EXCLUDE_FROM_ALL) endif() if(NOT TARGET ${target}) add_library(${target} ALIAS ${smallname}) endif() endif() endmacro() system_or_submodule(OXENC oxenc oxenc::oxenc liboxenc>=1.5.0 oxen-libquic/external/oxen-encoding) system_or_submodule(OXENLOGGING oxen-logging oxen::logging liboxen-logging>=1.2.0 oxen-libquic/external/oxen-logging) if(LOKINET_FULL) system_or_submodule(OXENMQ oxenmq oxenmq::oxenmq liboxenmq>=1.3 oxen-mq) endif() system_or_submodule(FMT fmt fmt::fmt fmt>=9 oxen-libquic/external/oxen-logging/fmt) set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_Install OFF CACHE INTERNAL "") system_or_submodule(NLOHMANN nlohmann_json nlohmann_json::nlohmann_json nlohmann_json>=3.7.0 nlohmann) if(LOKINET_HIVE) add_subdirectory(pybind11 EXCLUDE_FROM_ALL) endif() system_or_submodule(CLI11 CLI11 CLI11::CLI11 CLI11>=2.3.0 CLI11) if(LOKINET_PEERSTATS) add_library(sqlite_orm INTERFACE) target_include_directories(sqlite_orm SYSTEM INTERFACE sqlite_orm/include) if(NOT TARGET sqlite3) add_library(sqlite3 INTERFACE) pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3) target_link_libraries(sqlite3 INTERFACE PkgConfig::SQLITE3) endif() target_link_libraries(sqlite_orm INTERFACE sqlite3) endif() set(LIBQUIC_BUILD_TESTS OFF CACHE BOOL "") system_or_submodule(OXENQUIC quic oxen::quic liboxenquic>=1.6 oxen-libquic) # libcrypt defaults, only on with macos and non static linux set(default_libcrypt OFF) if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND NOT STATIC_LINK) pkg_check_modules(LIBCRYPT libcrypt IMPORTED_TARGET) if(LIBCRYPTO_FOUND) set(default_libcrypt ON) endif() endif() if(MACOS) set(default_libcrypt ON) endif() option(WITH_LIBCRYPT "enable fast password hash with libcrypt" ${default_libcrypt}) add_library(lokinet-libcrypt INTERFACE) if(WITH_LIBCRYPT) pkg_check_modules(LIBCRYPT libcrypt IMPORTED_TARGET REQUIRED) target_compile_definitions(lokinet-libcrypt INTERFACE LOKINET_HAVE_CRYPT) target_link_libraries(lokinet-libcrypt INTERFACE PkgConfig::LIBCRYPT) message(STATUS "using libcrypt ${LIBCRYPT_VERSION}") else() # TODO static build lib crypt? message(STATUS "not building with libcrypt") endif() ================================================ FILE: include/llarp.hpp ================================================ #pragma once #include #include #include namespace oxen::quic { class Loop; } namespace llarp { namespace vpn { class Platform; } struct Config; class Router; // Helper "context" that aids in starting up Lokinet. // // Note that this class is *not* thread-safe: only one thread should attempt to hold and // interact with it to manage Lokinet. // // TODO FIXME this class seems unnecessary, we should get rid of it. struct Context { std::unique_ptr router; explicit Context(bool embedded); ~Context(); // Starts Lokinet; returns as soon as Lokinet is up and running (or throws if startup // fails). The loop may be provided in order to use an existing loop, but otherwise a new // one will be started. void start(Config conf, std::shared_ptr loop = nullptr); // Waits for Lokinet to finish. Note that this does not *trigger* such a shutdown; for that // you would call `stop()` before this. void wait(); // Call this to deliver a signal, such as SIGTERM or SIGINT to stop Lokinet if currently // running. (This can be called from any thread). void signal(int sig); // Initiates Lokinet shutdown, and returns immediately (without waiting for shutdown). Call // `wait()` after this if you also want to wait for shutdown to complete. void stop(); bool is_up() const; bool is_stopping() const; // Returns true if Lokinet has stopped and `wait()` needs to be called to finish // destruction. bool is_waiting() const; bool looks_alive() const; int androidFD = -1; private: bool embedded; std::future lifetime_waiter; }; } // namespace llarp ================================================ FILE: include/lokinet/addr.h ================================================ #pragma once #include "context.h" #ifdef __cplusplus extern "C" { #endif /// get a free()-able null terminated string that holds our .loki address /// returns NULL if we dont have one right now char* EXPORT lokinet_address(struct lokinet_context*); #ifdef __cplusplus } #endif ================================================ FILE: include/lokinet/context.h ================================================ #pragma once #include "export.h" #include #include #include #ifdef __cplusplus extern "C" { #endif struct lokinet_context; /// allocate a new lokinet context struct lokinet_context* EXPORT lokinet_context_new(); /// free a context allocated by lokinet_context_new void EXPORT lokinet_context_free(struct lokinet_context*); /// spawn all the threads needed for operation and start running /// return 0 on success /// return non zero on fail int EXPORT lokinet_context_start(struct lokinet_context*); /// return 0 if we our endpoint has published on the network and is ready to send /// return -1 if we don't have enough paths ready /// retrun -2 if we look deadlocked /// retrun -3 if context was null or not started yet int EXPORT lokinet_status(struct lokinet_context*); /// wait at most N milliseconds for lokinet to build paths and get ready /// return 0 if we are ready /// return nonzero if we are not ready int EXPORT lokinet_wait_for_ready(int N, struct lokinet_context*); /// stop all operations on this lokinet context void EXPORT lokinet_context_stop(struct lokinet_context*); /// load a bootstrap RC from memory /// return 0 on success /// return non zero on fail int EXPORT lokinet_add_bootstrap_rc(const char*, size_t, struct lokinet_context*); #ifdef __cplusplus } #endif ================================================ FILE: include/lokinet/export.h ================================================ #pragma once #ifdef _WIN32 #define EXPORT __cdecl #else #define EXPORT #endif ================================================ FILE: include/lokinet/misc.h ================================================ #pragma once #include "export.h" #ifdef __cplusplus extern "C" { #endif /// change our network id globally across all contexts void EXPORT lokinet_set_netid(const char* netid); /// get our current netid /// must be free()'d after use const char* EXPORT lokinet_get_netid(); /// set log level /// possible values: trace, debug, info, warn, error, critical, none /// return 0 on success /// return non zero on fail int EXPORT lokinet_log_level(const char* level); /// Function pointer to invoke with lokinet log messages typedef void (*lokinet_logger_func)(const char* message, void* context); /// Optional function to call when flushing lokinet log messages; can be NULL if flushing is not /// meaningful for the logging system. typedef void (*lokinet_logger_sync)(void* context); /// set a custom logger function; it is safe (and often desirable) to call this before calling /// initializing lokinet via lokinet_context_new. void EXPORT lokinet_set_syncing_logger(lokinet_logger_func func, lokinet_logger_sync sync, void* context); /// shortcut for calling `lokinet_set_syncing_logger` with a NULL sync void EXPORT lokinet_set_logger(lokinet_logger_func func, void* context); /// @brief take in hex and turn it into base32z /// @return value must be free()'d later char* EXPORT lokinet_hex_to_base32z(const char* hex); #ifdef __cplusplus } #endif ================================================ FILE: include/lokinet/srv.h ================================================ #pragma once #include "context.h" #ifdef __cplusplus extern "C" { #endif // a single srv record struct lokinet_srv_record { /// the srv priority of the record uint16_t priority; /// the weight of this record uint16_t weight; /// null terminated string of the hostname char target[256]; /// the port to use int port; }; /// private members of a srv lookup struct lokinet_srv_lookup_private; /// the result of an srv lookup struct lokinet_srv_lookup_result { /// set to zero on success otherwise is the error code int error; /// pointer to internal members /// dont touch me struct lokinet_srv_lookup_private* internal; }; /// do a srv lookup on host for service /// caller MUST call lokinet_srv_lookup_done when they are done handling the result int EXPORT lokinet_srv_lookup( char* host, char* service, struct lokinet_srv_lookup_result* result, struct lokinet_context* ctx); /// a hook function to handle each srv record in a srv lookup result /// passes in NULL when we are at the end of iteration /// passes in void * user data /// hook should NOT free the record typedef void (*lokinet_srv_record_iterator)(struct lokinet_srv_record*, void*); /// iterate over each srv record in a lookup result /// user is passes into hook and called for each result and then with NULL as the result on the /// end of iteration void EXPORT lokinet_for_each_srv_record(struct lokinet_srv_lookup_result* result, lokinet_srv_record_iterator iter, void* user); /// free internal members of a srv lookup result after use of the result void EXPORT lokinet_srv_lookup_done(struct lokinet_srv_lookup_result* result); #ifdef __cplusplus } #endif ================================================ FILE: include/lokinet/stream.h ================================================ #pragma once #include "context.h" #ifdef __cplusplus extern "C" { #endif /// the result of a lokinet stream mapping attempt struct lokinet_stream_result { /// set to zero on success otherwise the error that happened /// use strerror(3) to get printable string of this error int error; /// the local ip address we mapped the remote endpoint to /// null terminated char local_address[256]; /// the local port we mapped the remote endpoint to int local_port; /// the id of the stream we created int stream_id; }; /// connect out to a remote endpoint /// remoteAddr is in the form of "name:port" /// localAddr is either NULL for any or in the form of "ip:port" to bind to an explicit address void EXPORT lokinet_outbound_stream( struct lokinet_stream_result* result, const char* remoteAddr, const char* localAddr, struct lokinet_context* context); /// stream accept filter determines if we should accept a stream or not /// return 0 to accept /// return -1 to explicitly reject /// return -2 to silently drop typedef int (*lokinet_stream_filter)(const char* remote, uint16_t port, void* userdata); /// set stream accepter filter /// passes user parameter into stream filter as void * /// returns stream id int EXPORT lokinet_inbound_stream_filter(lokinet_stream_filter acceptFilter, void* user, struct lokinet_context* context); /// simple stream acceptor /// simple variant of lokinet_inbound_stream_filter that maps port to localhost:port int EXPORT lokinet_inbound_stream(uint16_t port, struct lokinet_context* context); void EXPORT lokinet_close_stream(int stream_id, struct lokinet_context* context); #ifdef __cplusplus } #endif ================================================ FILE: include/lokinet/udp.h ================================================ #pragma once #include "context.h" #ifdef __cplusplus extern "C" { #endif /// information about a udp flow struct lokinet_udp_flowinfo { /// remote endpoint's .loki or .snode address char remote_host[256]; /// remote endpont's port uint16_t remote_port; /// the socket id for this flow used for i/o purposes and closing this socket int socket_id; }; /// a result from a lokinet_udp_bind call struct lokinet_udp_bind_result { /// a socket id used to close a lokinet udp socket int socket_id; }; /// flow acceptor hook, return 0 success, return nonzero with errno on failure typedef int (*lokinet_udp_flow_filter)( void* userdata, const struct lokinet_udp_flowinfo* remote_address, void** flow_userdata, int* timeout_seconds); /// callback to make a new outbound flow typedef void(lokinet_udp_create_flow_func)(void* userdata, void** flow_userdata, int* timeout_seconds); /// hook function for handling packets typedef void (*lokinet_udp_flow_recv_func)( const struct lokinet_udp_flowinfo* remote_address, const char* pkt_data, size_t pkt_length, void* flow_userdata); /// hook function for flow timeout typedef void (*lokinet_udp_flow_timeout_func)( const struct lokinet_udp_flowinfo* remote_address, void* flow_userdata); /// inbound listen udp socket /// expose udp port exposePort to the void //// /// @param filter MUST be non null, pointing to a flow filter for accepting new udp flows, /// called with user data /// /// @param recv MUST be non null, pointing to a packet handler function for each flow, called /// with per flow user data provided by filter function if accepted /// /// @param timeout MUST be non null, /// pointing to a cleanup function to clean up a stale flow, staleness determined by the value /// given by the filter function returns 0 on success /// /// @returns nonzero on error in which it is an errno value int EXPORT lokinet_udp_bind( uint16_t exposedPort, lokinet_udp_flow_filter filter, lokinet_udp_flow_recv_func recv, lokinet_udp_flow_timeout_func timeout, void* user, struct lokinet_udp_bind_result* result, struct lokinet_context* ctx); /// @brief establish a udp flow to remote endpoint /// /// @param create_flow the callback to create the new flow if we establish one /// /// @param user passed to new_flow as user data /// /// @param remote the remote address to establish to /// /// @param ctx the lokinet context to use /// /// @return 0 on success, non zero errno on fail int EXPORT lokinet_udp_establish( lokinet_udp_create_flow_func create_flow, void* user, const struct lokinet_udp_flowinfo* remote, struct lokinet_context* ctx); /// @brief send on an established flow to remote endpoint /// blocks until we have sent the packet /// /// @param flowinfo remote flow to use for sending /// /// @param ptr pointer to data to send /// /// @param len the length of the data /// /// @param ctx the lokinet context to use /// /// @returns 0 on success and non zero errno on fail int EXPORT lokinet_udp_flow_send( const struct lokinet_udp_flowinfo* remote, const void* ptr, size_t len, struct lokinet_context* ctx); /// @brief close a bound udp socket /// closes all flows immediately /// /// @param socket_id the bound udp socket's id /// /// @param ctx lokinet context void EXPORT lokinet_udp_close(int socket_id, struct lokinet_context* ctx); #ifdef __cplusplus } #endif ================================================ FILE: include/lokinet.h ================================================ #pragma once #include "lokinet/addr.h" #include "lokinet/context.h" #include "lokinet/misc.h" #include "lokinet/srv.h" #include "lokinet/stream.h" #include "lokinet/udp.h" ================================================ FILE: include/lokinet.hpp ================================================ #pragma once #include #include #include #include #include #include namespace llarp { struct Context; struct Config; } // namespace llarp namespace oxen::quic { class Loop; } namespace lokinet { enum class Network { MAINNET, TESTNET }; /// Metadata returned when establishing a session for a TCP or UDP tunnel. struct tunnel_info { /// The requested remote address. If an ONS entry was requested, this will be the resolved /// "fulladdress.loki" rather than the ONS entry value. std::string remote; /// The requested remote port. Packets sent to the `local_port` are delivered to this /// remote port, and returning packets from that remote back to the incoming source are /// routed back to the client and delivered to the source port that sent the original /// packet. Multiple connections to the same address are possible: each different source /// port establishes a separate connection (actual TCP connections for TCP, a remembered /// mapping for UDP). uint16_t remote_port; /// The bound local port. After establishing a lokinet session, clients connect (TCP) or /// send (UDP) to this port (on address 127.0.0.1) to reach the destination through lokinet. uint16_t local_port; /// A suggested maximum MTU for the connection. If the application supports a configurable /// MTU, this value is the recommended value that avoids some additional overhead from /// packet splitting, which can slightly reduce latency and jitter. If the application /// doesn't support MTU configuration then this value can simply be ignored and Lokinet will /// split any "too large" packets into two. uint16_t suggested_mtu; }; class Lokinet { std::unique_ptr context; struct path_ctor {}; Lokinet(path_ctor, const std::filesystem::path& p, std::shared_ptr loop); public: // Starts an embedded lokinet that loads the given string contents as a config file. explicit Lokinet(std::string config, std::shared_ptr existing_loop = nullptr); // Starts an embedded lokinet instance with extra configuration specified in the given // config file. (Templatized to avoid ambiguous implicit conversion from std::string // conflicting with the constructor above.) template FSPath> explicit Lokinet(const FSPath& config, std::shared_ptr existing_loop = nullptr) : Lokinet{path_ctor{}, config, std::move(existing_loop)} {} // Starts an embedded lokinet with default config that runs on the given network with // default settings. explicit Lokinet(Network network, std::shared_ptr existing_loop = nullptr); // Destructor stops the lokinet instance. The destructor blocks until shutdown is complete. ~Lokinet(); // Schedules the given callback to be fired when Lokinet edge connections are mostly // established (and thus Lokinet is ready to start building paths). If lokinet is already // established, this will schedule an immediate invocation of the callback. // // If persist is true then the callback will be stored and called *each* time Lokinet enters // the connected state (i.e. it will be called again if Lokinet loses all connectivity and // then regains connections). void on_connected(std::function callback, bool persist = false); // Schedules the given callback to be fired when Lokinet becomes fully disconnected, i.e. // loses all established edge connections. If persist is true then the callback will be // fired *each* time Lokinet transitions from connected to disconnected state. If Lokinet // is not currently connected then the callback will be scheduled immediately. void on_disconnected(std::function callback, bool persist = false); // Establishes a UDP session to the given remote (.loki or .snode) and port. When the // lokinet session to the remote is established, the callback is invoked with the info // corresponding to the session and tunnel. This call can happen instantly (before this // function call returns) if a session to the given address is already established, but // otherwise the callback will be called at some future point when the callback is // established. // // If a connection cannot be established for whatever reason, the `on_failed` callback is // invoked with a string giving a descriptive reason. Like `on_established`, it is possible // for this to fire immediately, such as for an unparseable address or if Lokinet can // determine immediately that the connection will fail. // // The callbacks must not block as they are called from Lokinet's logic thread (and so any // blocking will stall Lokinet). void establish_udp( std::string_view remote_view, uint16_t port, std::function on_established, std::function on_failed); // Simple synchronous wrapper around the above: this blocks until either `on_established` or // `on_failed` is called then returns the tunnel info (success) or throws the error message // (failure). This is provided for quick-and-dirty implementation code; generally code // should prefer the callback-based async version, above. tunnel_info establish_udp_blocking(std::string_view remote, uint16_t port); }; template Lokinet::Lokinet(const std::filesystem::path&, std::shared_ptr); } // namespace lokinet ================================================ FILE: jni/CMakeLists.txt ================================================ add_library(lokinet-android SHARED lokinet_config.cpp lokinet_daemon.cpp) target_link_libraries(lokinet-android lokinet-amalgum) ================================================ FILE: jni/java/src/network/loki/lokinet/LokinetConfig.java ================================================ package network.loki.lokinet; import java.nio.ByteBuffer; public class LokinetConfig { static { System.loadLibrary("lokinet-android"); } private static native ByteBuffer Obtain(String dataDir); private static native void Free(ByteBuffer buf); /*** load config file from disk */ public native boolean Load(); /*** save chages to disk */ public native boolean Save(); /** override default config value before loading from config file */ public native void AddDefaultValue(String section, String key, String value); private final ByteBuffer impl; public LokinetConfig(String dataDir) { impl = Obtain(dataDir); if(impl == null) throw new RuntimeException("cannot obtain config from "+dataDir); } public void finalize() { if (impl != null) { Free(impl); } } } ================================================ FILE: jni/java/src/network/loki/lokinet/LokinetDaemon.java ================================================ package network.loki.lokinet; import java.lang.Thread; import java.nio.ByteBuffer; import java.io.File; import android.net.VpnService; import android.util.Log; import android.content.Intent; import android.os.ParcelFileDescriptor; public class LokinetDaemon extends VpnService { static { System.loadLibrary("lokinet-android"); } private static native ByteBuffer Obtain(); private static native void Free(ByteBuffer buf); public native boolean Configure(LokinetConfig config); public native int Mainloop(); public native boolean IsRunning(); public native boolean Stop(); public native void InjectVPNFD(); public native int GetUDPSocket(); private static native String DetectFreeRange(); public native String DumpStatus(); public static final String LOG_TAG = "LokinetDaemon"; ByteBuffer impl = null; ParcelFileDescriptor iface; int m_FD = -1; int m_UDPSocket = -1; @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); if (IsRunning()) { Stop(); } if (impl != null) { Free(impl); impl = null; } } public int onStartCommand(Intent intent, int flags, int startID) { Log.d(LOG_TAG, "onStartCommand()"); if (!IsRunning()) { if (impl != null) { Free(impl); impl = null; } impl = Obtain(); if (impl == null) { Log.e(LOG_TAG, "got nullptr when creating llarp::Context in jni"); return START_NOT_STICKY; } String dataDir = getFilesDir().toString(); LokinetConfig config; try { config = new LokinetConfig(dataDir); } catch(RuntimeException ex) { Log.e(LOG_TAG, ex.toString()); return START_NOT_STICKY; } // FIXME: make these configurable String exitNode = "exit.loki"; String upstreamDNS = "1.1.1.1"; String ourRange = DetectFreeRange(); if(ourRange.isEmpty()) { Log.e(LOG_TAG, "cannot detect free range"); return START_NOT_STICKY; } // set up config values config.AddDefaultValue("network", "exit-node", exitNode); config.AddDefaultValue("network", "ifaddr", ourRange); config.AddDefaultValue("dns", "upstream", upstreamDNS); if (!config.Load()) { Log.e(LOG_TAG, "failed to load (or create) config file at: " + dataDir + "/lokinet.ini"); return START_NOT_STICKY; } VpnService.Builder builder = new VpnService.Builder(); builder.setMtu(1500); String[] parts = ourRange.split("/"); String ourIPv4 = parts[0]; int ourMask = Integer.parseInt(parts[1]); // 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); iface = builder.establish(); if (iface == null) { Log.e(LOG_TAG, "VPN Interface from builder.establish() came back null"); return START_NOT_STICKY; } m_FD = iface.detachFd(); InjectVPNFD(); new Thread(() -> { Configure(config); m_UDPSocket = GetUDPSocket(); protect(m_UDPSocket); Mainloop(); }).start(); Log.d(LOG_TAG, "started successfully!"); } else { Log.d(LOG_TAG, "already running"); } return START_STICKY; } } ================================================ FILE: jni/lokinet_config.cpp ================================================ #include "lokinet_jni_common.hpp" #include "network_loki_lokinet_LokinetConfig.h" #include #include extern "C" { JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv* env, jclass, jstring dataDir) { auto conf = VisitStringAsStringView( env, dataDir, [](std::string_view val) -> llarp::Config* { return new llarp::Config{val}; }); if (conf == nullptr) return nullptr; return env->NewDirectByteBuffer(conf, sizeof(llarp::Config)); } JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv* env, jclass, jobject buf) { auto ptr = FromBuffer(env, buf); delete ptr; } JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv* env, jobject self) { auto conf = GetImpl(env, self); if (conf == nullptr) return JNI_FALSE; if (conf->Load()) { return JNI_TRUE; } return JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv* env, jobject self) { auto conf = GetImpl(env, self); if (conf == nullptr) return JNI_FALSE; try { conf->Save(); } catch (...) { return JNI_FALSE; } return JNI_TRUE; } JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_AddDefaultValue( JNIEnv* env, jobject self, jstring section, jstring key, jstring value) { auto convert = [](std::string_view str) -> std::string { return std::string{str}; }; const auto sect = VisitStringAsStringView(env, section, convert); const auto k = VisitStringAsStringView(env, key, convert); const auto v = VisitStringAsStringView(env, value, convert); auto conf = GetImpl(env, self); if (conf) { conf->AddDefault(sect, k, v); } } } ================================================ FILE: jni/lokinet_daemon.cpp ================================================ #include "lokinet_jni_common.hpp" #include "network_loki_lokinet_LokinetDaemon.h" #include #include #include extern "C" { JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv* env, jclass) { auto* ptr = new llarp::Context(); if (ptr == nullptr) return nullptr; return env->NewDirectByteBuffer(ptr, sizeof(llarp::Context)); } JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv* env, jclass, jobject buf) { auto ptr = FromBuffer(env, buf); delete ptr; } JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv* env, jobject self, jobject conf) { auto ptr = GetImpl(env, self); auto config = GetImpl(env, conf); if (ptr == nullptr || config == nullptr) return JNI_FALSE; try { llarp::RuntimeOptions opts{}; // janky make_shared deep copy because jni + shared pointer = scary ptr->Configure(std::make_shared(*config)); ptr->Setup(opts); } catch (...) { return JNI_FALSE; } return JNI_TRUE; } JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv* env, jobject self) { auto ptr = GetImpl(env, self); if (ptr == nullptr) return -1; llarp::RuntimeOptions opts{}; return ptr->Run(opts); } JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv* env, jobject self) { auto ptr = GetImpl(env, self); return (ptr != nullptr && ptr->IsUp()) ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv* env, jobject self) { auto ptr = GetImpl(env, self); if (ptr == nullptr) return JNI_FALSE; if (not ptr->IsUp()) return JNI_FALSE; ptr->CloseAsync(); ptr->Wait(); return ptr->IsUp() ? JNI_FALSE : JNI_TRUE; } JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv* env, jobject self) { if (auto ptr = GetImpl(env, self)) ptr->androidFD = GetObjectMemberAsInt(env, self, "m_FD"); } JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self) { if (auto ptr = GetImpl(env, self); ptr and ptr->router) return ptr->router->outbound_socket(); return -1; } JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass) { std::string rangestr{}; if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange()) { rangestr = maybe->ToString(); } return env->NewStringUTF(rangestr.c_str()); } JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv* env, jobject self) { std::string status{}; if (auto ptr = GetImpl(env, self)) { if (ptr->IsUp()) { std::promise result; ptr->CallSafe([&result, router = ptr->router]() { const auto status = router->ExtractStatus(); result.set_value(status.dump()); }); status = result.get_future().get(); } } return env->NewStringUTF(status.c_str()); } } ================================================ FILE: jni/lokinet_jni_common.hpp ================================================ #pragma once #include #include #include /// visit string as native bytes /// jvm uses some unholy encoding internally so we convert it to utf-8 template static T VisitStringAsStringView(JNIEnv* env, jobject str, V visit) { const jclass stringClass = env->GetObjectClass(str); const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); const jstring charsetName = env->NewStringUTF("UTF-8"); const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod(str, getBytes, charsetName); env->DeleteLocalRef(charsetName); const size_t length = env->GetArrayLength(stringJbytes); jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); T result = visit(std::string_view((const char*)pBytes, length)); env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); env->DeleteLocalRef(stringJbytes); return result; } /// cast jni buffer to T * template static T* FromBuffer(JNIEnv* env, jobject o) { if (o == nullptr) return nullptr; return static_cast(env->GetDirectBufferAddress(o)); } /// get T * from object member called membername template static T* FromObjectMember(JNIEnv* env, jobject self, const char* membername) { jclass cl = env->GetObjectClass(self); jfieldID name = env->GetFieldID(cl, membername, "Ljava/nio/ByteBuffer;"); jobject buffer = env->GetObjectField(self, name); return FromBuffer(env, buffer); } /// visit object string member called membername as bytes template static T VisitObjectMemberStringAsStringView(JNIEnv* env, jobject self, const char* membername, V v) { jclass cl = env->GetObjectClass(self); jfieldID name = env->GetFieldID(cl, membername, "Ljava/lang/String;"); jobject str = env->GetObjectField(self, name); return VisitStringAsStringView(env, str, v); } /// get object member int called membername template Int_t GetObjectMemberAsInt(JNIEnv* env, jobject self, const char* membername) { jclass cl = env->GetObjectClass(self); jfieldID name = env->GetFieldID(cl, membername, "I"); return env->GetIntField(self, name); } /// get implementation on jni type template T* GetImpl(JNIEnv* env, jobject self) { return FromObjectMember(env, self, "impl"); } ================================================ FILE: jni/network_loki_lokinet_LokinetConfig.h ================================================ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class network_loki_lokinet_LokinetConfig */ #ifndef _Included_network_loki_lokinet_LokinetConfig #define _Included_network_loki_lokinet_LokinetConfig #ifdef __cplusplus extern "C" { #endif /* * Class: network_loki_lokinet_LokinetConfig * Method: Obtain * Signature: (Ljava/lang/String;)Ljava/nio/ByteBuffer; */ JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv*, jclass, jstring); /* * Class: network_loki_lokinet_LokinetConfig * Method: Free * Signature: (Ljava/nio/ByteBuffer;)V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv*, jclass, jobject); /* * Class: network_loki_lokinet_LokinetConfig * Method: Load * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetConfig * Method: Save * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetConfig * Method: AddDefaultValue * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_AddDefaultValue(JNIEnv*, jobject, jstring, jstring, jstring); #ifdef __cplusplus } #endif #endif ================================================ FILE: jni/network_loki_lokinet_LokinetDaemon.h ================================================ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class network_loki_lokinet_LokinetDaemon */ #ifndef _Included_network_loki_lokinet_LokinetDaemon #define _Included_network_loki_lokinet_LokinetDaemon #ifdef __cplusplus extern "C" { #endif /* * Class: network_loki_lokinet_LokinetDaemon * Method: Obtain * Signature: ()Ljava/nio/ByteBuffer; */ JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv*, jclass); /* * Class: network_loki_lokinet_LokinetDaemon * Method: Free * Signature: (Ljava/nio/ByteBuffer;)V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv*, jclass, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: Configure * Signature: (Lnetwork/loki/lokinet/LokinetConfig;)Z */ JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv*, jobject, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: Mainloop * Signature: ()I */ JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: IsRunning * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: Stop * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: InjectVPNFD * Signature: ()V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: GetUDPSocket * Signature: ()I */ JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetDaemon * Method: DetectFreeRange * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv*, jclass); /* * Class: network_loki_lokinet_LokinetDaemon * Method: DumpStatus * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv*, jobject); #ifdef __cplusplus } #endif #endif ================================================ FILE: jni/network_loki_lokinet_LokinetVPN.h ================================================ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class network_loki_lokinet_LokinetVPN */ #ifndef _Included_network_loki_lokinet_LokinetVPN #define _Included_network_loki_lokinet_LokinetVPN #ifdef __cplusplus extern "C" { #endif /* * Class: network_loki_lokinet_LokinetVPN * Method: PacketSize * Signature: ()I */ JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetVPN_PacketSize(JNIEnv*, jclass); /* * Class: network_loki_lokinet_LokinetVPN * Method: Alloc * Signature: ()Ljava/nio/Buffer; */ JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetVPN_Alloc(JNIEnv*, jclass); /* * Class: network_loki_lokinet_LokinetVPN * Method: Free * Signature: (Ljava/nio/Buffer;)V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_Free(JNIEnv*, jclass, jobject); /* * Class: network_loki_lokinet_LokinetVPN * Method: Stop * Signature: ()V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_Stop(JNIEnv*, jobject); /* * Class: network_loki_lokinet_LokinetVPN * Method: ReadPkt * Signature: (Ljava/nio/ByteBuffer;)I */ JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetVPN_ReadPkt(JNIEnv*, jobject, jobject); /* * Class: network_loki_lokinet_LokinetVPN * Method: WritePkt * Signature: (Ljava/nio/ByteBuffer;)Z */ JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetVPN_WritePkt(JNIEnv*, jobject, jobject); /* * Class: network_loki_lokinet_LokinetVPN * Method: SetInfo * Signature: (Lnetwork/loki/lokinet/LokinetVPN/VPNInfo;)V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_SetInfo(JNIEnv*, jobject, jobject); #ifdef __cplusplus } #endif #endif ================================================ FILE: jni/network_loki_lokinet_LokinetVPN_VPNInfo.h ================================================ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class network_loki_lokinet_LokinetVPN_VPNInfo */ #ifndef _Included_network_loki_lokinet_LokinetVPN_VPNInfo #define _Included_network_loki_lokinet_LokinetVPN_VPNInfo #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif ================================================ FILE: jni/network_loki_lokinet_Lokinet_JNI.h ================================================ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class network_loki_lokinet_Lokinet_JNI */ #ifndef _Included_network_loki_lokinet_Lokinet_JNI #define _Included_network_loki_lokinet_Lokinet_JNI #ifdef __cplusplus extern "C" { #endif /* * Class: network_loki_lokinet_Lokinet_JNI * Method: getABICompiledWith * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getABICompiledWith(JNIEnv*, jclass); /* * Class: network_loki_lokinet_Lokinet_JNI * Method: startLokinet * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_startLokinet(JNIEnv*, jclass, jstring); JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getIfAddr(JNIEnv*, jclass); JNIEXPORT jint JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getIfRange(JNIEnv*, jclass); /* * Class: network_loki_lokinet_Lokinet_JNI * Method: stopLokinet * Signature: ()V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_stopLokinet(JNIEnv*, jclass); JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_setVPNFileDescriptor(JNIEnv*, jclass, jint, jint); /* * Class: network_loki_lokinet_Lokinet_JNI * Method: onNetworkStateChanged * Signature: (Z)V */ JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_onNetworkStateChanged(JNIEnv*, jclass, jboolean); #ifdef __cplusplus } #endif #endif ================================================ FILE: jni/readme.md ================================================ jni binding for lokinet vpn using android vpn api ================================================ FILE: llarp/CMakeLists.txt ================================================ include(Version) # Add an internal lokinet static library target, enables LTO (if enabled) on the target, # and links it to the common lokinet-base interface. # Invoke with the target/library name (e.g. "lokinet-foo") and list of source files, e.g. # lokinet_add_library(lokinet-foo foo/source1.cpp foo/source2.cpp) function(lokinet_add_library libname) add_library(${libname} STATIC ${ARGN}) target_link_libraries(${libname} PUBLIC lokinet-base PRIVATE lokinet-base-internal) enable_lto(${libname}) endfunction() lokinet_add_library(lokinet-cryptography crypto/crypto.cpp crypto/key_manager.cpp crypto/keys.cpp crypto/types.cpp ) if(LOKINET_FULL) lokinet_add_library(lokinet-exit-auth auth/auth.cpp auth/file.cpp auth/session.cpp ) endif() # Functional objects use by lokinet-core and other libraries # needed by vpn/ router/ rpc/ handlers/ net/ link/ lokinet_add_library(lokinet-core-utils handlers/session.cpp messages/common.cpp messages/dht.cpp messages/fetch.cpp messages/path.cpp ) if (LOKINET_FULL) target_sources(lokinet-core-utils PRIVATE handlers/tun.cpp vpn/egres_packet_router.cpp) endif() lokinet_add_library(lokinet-core context.cpp link/connection.cpp link/endpoint.cpp link/link_manager.cpp router/router.cpp session/session.cpp ) if (LOKINET_FULL) target_sources(lokinet-core PRIVATE router/route_poker.cpp consensus/reachability_testing.cpp ) endif() if (LOKINET_FULL) lokinet_add_library(lokinet-rpc rpc/json_binary_proxy.cpp rpc/json_conversions.cpp rpc/oxend_rpc.cpp rpc/rpc_request_parser.cpp rpc/rpc_server.cpp ) endif() # config, crypto, timeplace, nodedb, net, router lokinet_add_library(lokinet-utils ${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp util/buffer.cpp util/file.cpp util/logging.cpp util/mem.cpp util/str.cpp util/thread/queue_manager.cpp util/thread/threading.cpp util/time.cpp util/zstd.cpp ) add_dependencies(lokinet-utils genversion) lokinet_add_library(lokinet-contact contact/client_contact.cpp contact/client_intro.cpp contact/contactdb.cpp contact/relay_contact.cpp contact/router_id.cpp contact/sns.cpp ) # Network interface files used for mediating ip traffic lokinet_add_library(lokinet-ip net/ip_packet.cpp net/policy.cpp net/utils.cpp ) # Addressing and event loop files used by lokinet-core and other libraries # needed by rpc/ link/ config/ path/ dht/ lokinet_add_library(lokinet-addressing address/address.cpp address/ip_range.cpp address/utils.cpp ev/fd_poller.cpp ev/tcp.cpp ) lokinet_add_library(lokinet-dns dns/srv_data.cpp ) if (LOKINET_FULL) # lokinet-platform holds all platform specific code lokinet_add_library(lokinet-platform vpn/packet_router.cpp vpn/platform.cpp ) if (ANDROID) target_sources(lokinet-platform PRIVATE android/ifaddrs.c util/nop_service_manager.cpp) endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux") target_sources(lokinet-platform PRIVATE linux/dbus.cpp) if(WITH_SYSTEMD) target_sources(lokinet-platform PRIVATE linux/sd_service_manager.cpp) else() target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp) endif() endif() if (WIN32) target_sources(lokinet-platform PRIVATE net/win32.cpp vpn/win32.cpp win32/service_manager.cpp win32/exec.cpp win32/dll.cpp win32/exception.cpp win32/wintun.cpp win32/windivert.cpp) target_include_directories(lokinet-platform PRIVATE ${CMAKE_BINARY_DIR}/wintun/include/ ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/include/) else() target_sources(lokinet-platform PRIVATE net/posix.cpp) endif() if(APPLE) add_subdirectory(apple) target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp) endif() # lokinet-dns is the dns parsing and hooking library that we use to # parse modify and reconstitute dns wire proto, dns queries and RR target_sources(lokinet-dns PRIVATE dns/message.cpp # dns/server dns/name.cpp # srv_data, question, rr dns/platform.cpp dns/question.cpp # message dns/rr.cpp dns/serialize.cpp dns/server.cpp ) # platform specific bits and bobs for setting dns if(WITH_SYSTEMD) target_sources(lokinet-dns PRIVATE dns/nm_platform.cpp dns/sd_platform.cpp) endif() endif() # lokinet-nodedb holds all types and logic for storing parsing and constructing # nodedb data published to the network and versions of it stored locally lokinet_add_library(lokinet-nodedb nodedb.cpp profiling.cpp # path, router, service::endpoint ) set(BOOTSTRAP_FALLBACKS) foreach(bs IN ITEMS MAINNET TESTNET) if(LOKINET_BOOTSTRAP_FALLBACK_${bs}) message(STATUS "Building with ${bs} fallback bootstrap path \"${LOKINET_BOOTSTRAP_FALLBACK_${bs}}\"") file(READ "${LOKINET_BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" bs_data "${bs_data}") set(BOOTSTRAP_FALLBACKS "${BOOTSTRAP_FALLBACKS} {NetID::${bs}, \"${bs_data}\"sv},\n") endif() endforeach() configure_file("nodedb-bootstraps.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/nodedb-bootstraps.cpp" @ONLY) target_sources(lokinet-nodedb PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/nodedb-bootstraps.cpp") # lokinet-config is for all configuration types and parsers lokinet_add_library(lokinet-config config/config.cpp config/definition.cpp config/ini.cpp ) # All path objects; link directly to lokinet-core lokinet_add_library(lokinet-path path/build_stats.cpp path/hopid.cpp path/path.cpp path/path_context.cpp path/path_handler.cpp path/transit_hop.cpp ) if(LOKINET_DEBUG_PATH_SEED) target_compile_definitions(lokinet-nodedb PRIVATE LOKINET_DEBUG_PATH_SEED) target_compile_definitions(lokinet-config PRIVATE LOKINET_DEBUG_PATH_SEED) target_compile_definitions(lokinet-path PRIVATE LOKINET_DEBUG_PATH_SEED) endif() # Link libraries to their internals target_link_libraries(lokinet-nodedb PUBLIC lokinet-addressing lokinet-cryptography) target_link_libraries(lokinet-contact PUBLIC lokinet-dns) if(LOKINET_FULL) target_link_libraries(lokinet-rpc PUBLIC lokinet-contact lokinet-config) endif() target_link_libraries(lokinet-addressing PUBLIC lokinet-utils lokinet-cryptography lokinet-contact lokinet-ip) target_link_libraries(lokinet-config PUBLIC lokinet-cryptography lokinet-addressing) if(LOKINET_FULL) target_link_libraries(lokinet-platform PUBLIC lokinet-cryptography lokinet-ip) target_link_libraries(lokinet-config PUBLIC lokinet-exit-auth) endif() target_link_libraries(lokinet-dns PUBLIC lokinet-cryptography lokinet-config ) if(LOKINET_FULL) target_link_libraries(lokinet-dns PUBLIC lokinet-platform) endif() target_link_libraries(lokinet-path PUBLIC lokinet-addressing ) target_link_libraries(lokinet-core-utils PUBLIC lokinet-config lokinet-dns ) if(LOKINET_FULL) target_link_libraries(lokinet-core-utils PUBLIC lokinet-rpc lokinet-platform ) endif() target_link_libraries(lokinet-cryptography PUBLIC lokinet-libcrypt ) if(LOKINET_FULL) target_link_libraries(lokinet-exit-auth PUBLIC lokinet-core-utils lokinet-cryptography) endif() target_link_libraries(lokinet-utils PUBLIC lokinet-cryptography ) target_link_libraries(lokinet-core PUBLIC lokinet-core-utils lokinet-nodedb lokinet-path ) if(LOKINET_FULL) target_link_libraries(lokinet-core PUBLIC lokinet-exit-auth) endif() target_link_libraries(lokinet-base INTERFACE Threads::Threads oxenc::oxenc oxen::logging sodium ) if(LOKINET_FULL) target_link_libraries(lokinet-base INTERFACE libunbound oxenmq::oxenmq) endif() # libevent if(NOT TARGET libevent::core) add_library(libevent_core INTERFACE) pkg_check_modules(LIBEVENT_core libevent_core>=2.1 IMPORTED_TARGET REQUIRED) target_link_libraries(libevent_core INTERFACE PkgConfig::LIBEVENT_core) add_library(libevent::core ALIAS libevent_core) endif() target_link_libraries(lokinet-addressing PRIVATE libevent::core) # libzstd if(TARGET libzstd::static) # static dep or libsession-util's embedded static target target_link_libraries(lokinet-utils PRIVATE libzstd::static) else() pkg_check_modules(LIBZSTD libzstd>=1.3 IMPORTED_TARGET REQUIRED) target_link_libraries(lokinet-utils PRIVATE PkgConfig::LIBZSTD) endif() add_library(liblokinet lokinet.cpp) target_link_libraries(liblokinet PUBLIC lokinet-core) set_target_properties(liblokinet PROPERTIES OUTPUT_NAME lokinet) if(BUILD_SHARED_LIBS AND LOKINET_VERSION_SO) set_target_properties(liblokinet PROPERTIES VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") endif() if(WIN32) target_link_libraries(liblokinet PUBLIC ws2_32 iphlpapi -fstack-protector) install(TARGETS liblokinet DESTINATION bin COMPONENT liblokinet) elseif(NOT APPLE) include(GNUInstallDirs) install(TARGETS liblokinet LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT liblokinet) endif() add_library(lokinet::liblokinet ALIAS liblokinet) file(GLOB_RECURSE docs_SRC */*.hpp *.hpp) set(DOCS_SRC ${docs_SRC} PARENT_SCOPE) ================================================ FILE: llarp/address/address.cpp ================================================ #include "address.hpp" #include #include #include namespace llarp { NetworkAddress::NetworkAddress(std::string_view arg) { if (arg.ends_with(TLD::SNODE)) { _is_client = false; arg.remove_suffix(TLD::SNODE.size()); } else if (arg.ends_with(TLD::LOKI)) { _is_client = true; arg.remove_suffix(TLD::LOKI.size()); } else { throw std::invalid_argument{ "Invalid network address '{}': expected *{} or *{}"_format(arg, TLD::LOKI, TLD::SNODE)}; } if (!_pubkey.from_base32z(arg)) throw std::invalid_argument{"Invalid network address '{}{}': expected full pubkey"_format( arg, _is_client ? TLD::LOKI : TLD::SNODE)}; } NetworkAddress::NetworkAddress(std::string_view arg, bool is_client) : _is_client{is_client} { if (!_pubkey.from_base32z(arg)) throw std::invalid_argument{"Invalid pubkey passed to NetworkAddress constructor: {}"_format(arg)}; } } // namespace llarp ================================================ FILE: llarp/address/address.hpp ================================================ #pragma once #include "utils.hpp" #include #include #include namespace llarp { /// Combines a pubkey and client/snode flag to represent a generic (client or snode) address. struct NetworkAddress { private: RouterID _pubkey{}; bool _is_client{false}; public: NetworkAddress() = default; // Constructs from a full network address ending in '.loki' or '.snode' (but *not* an ONS // entry). Throws std::invalid_argument if invalid. explicit NetworkAddress(std::string_view addr); // Constructs from a full network address (base32z-encoded pubkey) *not* ending in .loki or // .snode. The client or snode status is determined by the bool. NetworkAddress(std::string_view addr, bool is_client); // Constructs from a pubkey and flag indicating whether this is a client (true) or snode // (false). NetworkAddress(const RouterID& rid, bool is_client) : _pubkey{rid}, _is_client{is_client} {} bool operator==(const NetworkAddress& other) const { return std::tie(_pubkey, _is_client) == std::tie(other._pubkey, other._is_client); } bool empty() const { return _pubkey.is_zero(); } bool client() const { return _is_client; } bool relay() const { return !_is_client; } const RouterID& router_id() const { return _pubkey; } // Returns a log proxy object that prints a shortened part of the pubkey: auto short_name() const { return _pubkey.short_string(); } std::string name() const { return _pubkey.to_string(); } std::string to_string() const { return name().append(_is_client ? TLD::LOKI : TLD::SNODE); } static constexpr bool to_string_formattable = true; }; } // namespace llarp namespace std { template <> struct hash { size_t operator()(const llarp::NetworkAddress& r) const { return llarp::AlignedHasher{}(r.router_id()); } }; } // namespace std ================================================ FILE: llarp/address/ip_range.cpp ================================================ #include "ip_range.hpp" #include #include #include #include #include #include namespace llarp { static auto logcat = log::Cat("iprange"); template static std::conditional_t parse_ip_net( std::string_view address, std::optional default_mask) { auto mask_pos = address.find('/'); std::string addr{address.substr(0, mask_pos)}; quic::Address a{addr, 0}; if (a.is_ipv4() != is_ipv4) throw std::invalid_argument{"Cannot construct an IPv{} range from a IPv{} address"_format( is_ipv4 ? "4" : "6", is_ipv4 ? "6" : "4")}; uint8_t mask; if (mask_pos == std::string::npos) { if (default_mask) mask = *default_mask; else throw std::invalid_argument{"Invalid IP range: /N network mask is required"}; } else if (!parse_int(address.substr(mask_pos + 1), mask) || mask > (is_ipv4 ? 32 : 128)) { throw std::invalid_argument{"Invalid IP range: {} is not a valid IPv{} network mask"_format( address.substr(mask_pos), is_ipv4 ? "4" : "6")}; } if constexpr (is_ipv4) return {a.to_ipv4(), mask}; else return {a.to_ipv6(), mask}; } ipv4_net parse_ipv4_net(std::string_view address, std::optional default_mask) { return parse_ip_net(address, default_mask); } ipv6_net parse_ipv6_net(std::string_view address, std::optional default_mask) { return parse_ip_net(address, default_mask); } ipv4_range parse_ipv4_range(std::string_view address, std::optional default_mask) { return parse_ipv4_net(address, default_mask).to_range(); } ipv6_range parse_ipv6_range(std::string_view address, std::optional default_mask) { return parse_ipv6_net(address, default_mask).to_range(); } // Encoded as ABCDM for each byte of A.B.C.D/M std::string encode(const ipv4_range& r) { std::string enc; enc.resize(5); oxenc::write_host_as_big(r.ip.addr, enc.data()); enc[4] = static_cast(r.mask); return enc; } ipv4_range decode_ipv4_range(std::string_view encoded) { if (encoded.size() != 5) throw std::invalid_argument{"Invalid ipv4 range encoding"}; ipv4_range result; result.mask = static_cast(encoded[4]); if (result.mask > 32) throw std::invalid_argument{"Invalid ipv4 range encoded netmask"}; result.ip.addr = oxenc::load_big_to_host(encoded.data()); return result; } // Encode as *either* the full IPv6 address (16 bytes) followed by the netmask byte, or the // first 8 bytes of the IPv6 address followed by the netmask byte. In the latter case, the // lower 8 bytes are implied to be 0. std::string encode(const ipv6_range& r) { std::string enc; enc.resize(r.ip.lo ? 17 : 9); oxenc::write_host_as_big(r.ip.hi, enc.data()); if (r.ip.lo) oxenc::write_host_as_big(r.ip.lo, enc.data() + 8); enc.back() = static_cast(r.mask); return enc; } ipv6_range decode_ipv6_range(std::string_view encoded) { if (encoded.size() != 9 && encoded.size() != 17) throw std::invalid_argument{"Invalid ipv6 range encoding"}; ipv6_range result; result.mask = static_cast(encoded.back()); if (result.mask > 128) throw std::invalid_argument{"Invalid ipv6 range encoded netmask"}; result.ip.hi = oxenc::load_big_to_host(encoded.data()); result.ip.lo = encoded.size() == 17 ? oxenc::load_big_to_host(encoded.data() + 8) : 0; return result; } std::variant decode_ip_range(std::string_view encoded) { switch (encoded.size()) { case 5: return decode_ipv4_range(encoded); case 9: case 17: return decode_ipv6_range(encoded); default: throw std::invalid_argument{"Invalid encoded ip range"}; } } ipv4_net to_ipv4_net(const quic::Address& addr, uint8_t mask) { if (!addr.is_ipv4()) throw std::invalid_argument{"Cannot construct an ipv4_net from an IPv6 address"}; if (mask > 32) throw std::invalid_argument{"{} is not a valid IPv4 network mask"_format(mask)}; return {addr.to_ipv4(), mask}; } ipv6_net to_ipv6_net(quic::Address addr, uint8_t mask) { if (!addr.is_ipv6()) throw std::invalid_argument{"Cannot construct an ipv6_net from an IPv4 address"}; if (mask > 128) throw std::invalid_argument{"{} is not a valid IPv6 network mask"_format(mask)}; return {addr.to_ipv6(), mask}; } static std::optional find_ipv4_net( uint32_t start, uint32_t end, uint8_t mask, const std::vector& exclude) { uint32_t step = uint32_t{1} << (32 - mask); auto ret = std::make_optional(); auto& net = *ret; auto& addr = net.ip.addr; ret->mask = mask; for (addr = start + 1; addr < end; addr += step) if (std::ranges::none_of( exclude, [&net](const auto& e) { return e.contains(net.ip) || net.contains(e.ip); })) return ret; ret.reset(); return ret; } std::optional find_private_ipv4_net(const std::vector& exclude, uint8_t mask) { if (mask < 8) { log::warning(logcat, "Cannot auto-detect IPv4 private networks larger than /8"); return std::nullopt; } // Cut off the smallest block we search to /20 (4096 addresses) just so that this doesn't // take an excessive amount of time if there are no free address spaces. We still give back // whatever you requested, if smaller, we just don't search the smaller spaces. That is, if // you request a /24 and 10.0.0.0/24 is already assigned then the next thing we would try is // 10.0.16.0/24 rather than 10.0.1.0/24. (There are 4368 distinct private range /20 // blocks). auto search_mask = std::min(18, mask); std::optional ret; // Start looking in the 172.16.x.x - 172.31.x.x range first as it is the least commonly // used, then 10.x.x.x, then finally the tiny 192.168.x.x range. if (mask >= 12) ret = find_ipv4_net((172 << 24) | (16 << 16), (172 << 24) | (32 << 16), search_mask, exclude); if (!ret && mask >= 8) ret = find_ipv4_net((10 << 24), (11 << 24), search_mask, exclude); if (!ret && mask >= 16) ret = find_ipv4_net((192 << 24) | (168 << 16), (192 << 24) | (169 << 16), search_mask, exclude); if (ret) ret->mask = mask; // In case we used a larger search_mask return ret; } std::optional find_private_ipv6_net(const std::vector& exclude, uint8_t mask) { // This /48 is registered for Lokinet in the IPv6 ULA registry (https://ula.ungleich.ch/): constexpr uint64_t start = 0xfd2e'6c6f'6b69'0000; // 2e 6c 6f 6b 69 == . l o k i constexpr uint64_t end = start + 0x1'0000; if (mask < 48) { log::warning(logcat, "Cannot auto-detect IPv6 private networks larger than /48"); return std::nullopt; } // First do a preliminary check that none of the excluded ranges covers the entire /48, // because if so, we simply can't succeed. { ipv6 min, max; min.hi = start; max.hi = end - 1; max.lo = 0xffff'ffff'ffff'ffff; if (std::ranges::any_of( exclude, [&min, &max](const auto& e) { return e.contains(min) && e.contains(max); })) { log::warning( logcat, "Failed to find a free private /64 IPv6 range: the entire {}/48 range is unavailable", min); return std::nullopt; } } // Cut off the smallest block we search to /64 just so that this doesn't take an excessive // amount of time, and because we can do it with just uint64_t math. We still give back // whatever you requested, if smaller, we just don't search the smaller spaces. auto search_mask = std::min(64, mask); uint64_t hi_step = 1 << (64 - search_mask); auto ret = std::make_optional(); auto& net = *ret; net.ip.lo = 1; auto& hi = net.ip.hi; for (hi = start; hi < end; hi += hi_step) { if (std::ranges::none_of( exclude, [&net](const auto& e) { return e.contains(net.ip) || net.contains(e.ip); })) { ret->mask = mask; // In case we enlarged it for searching return ret; } } ret.reset(); return ret; } } // namespace llarp ================================================ FILE: llarp/address/ip_range.hpp ================================================ #pragma once #include "types.hpp" #include namespace llarp { // Takes an address with an embedded "/mask" at the end and splits it into address and mask. // Throws std::invalid_argument if not parseable as an ADDR/MASK value. If default_mask is // given then the mask is optional, and the given default will be used if the mask is omitted // from the string. (Otherwise the mask must be provided in the string). // // Throws on invalid input. ipv4_net parse_ipv4_net(std::string_view address, std::optional default_mask = std::nullopt); ipv6_net parse_ipv6_net(std::string_view address, std::optional default_mask = std::nullopt); // Exactly the same as above but returns a range (which differs from a net in that it ignores // the IP and always uses the base IP). ipv4_range parse_ipv4_range(std::string_view address, std::optional default_mask = std::nullopt); ipv6_range parse_ipv6_range(std::string_view address, std::optional default_mask = std::nullopt); // Extracts the ipv4 address from a quic::Address, applies the netmask, and returns in an // ipv{4,6}_{net,range}. Throws if the Address contains the wrong type (ipv6 when ipv4 wanted or vice // versa). ipv4_net to_ipv4_net(const quic::Address& addr, uint8_t mask); ipv6_net to_ipv6_net(const quic::Address& addr, uint8_t mask); ipv4_net to_ipv4_range(const quic::Address& addr, uint8_t mask); ipv6_net to_ipv6_range(const quic::Address& addr, uint8_t mask); // binary encoding of an IP range, such as for exit policy ranges in a CC. std::string encode(const ipv4_range& r); std::string encode(const ipv6_range& r); ipv4_range decode_ipv4_range(std::string_view encoded); ipv6_range decode_ipv6_range(std::string_view decoded); std::variant decode_ip_range(std::string_view encoded); /// IPRangeIterator - walks through an IP range template struct IPRangeIterator { static constexpr bool is_ipv4 = IPv4; using ip_t = std::conditional_t; using ip_net_t = std::conditional_t; private: ip_t _curr, _base, _last; public: IPRangeIterator() = default; // Creates a range that starts at the current IP of range, increments up to the max // pre-broadcast address, and resets to the ".1" address of the range. explicit IPRangeIterator(const ip_net_t& net) : _curr{net.ip}, _base{_curr.to_base(net.mask)}, _last{net.max_ip()} {} // Returns the next ip address in the iterating range; returns nullopt if range is exhausted std::optional next_ip() { if (_curr == _last) return std::nullopt; if (auto next = _curr.next_ip()) return _curr = *next; return std::nullopt; } // Resets the range to the base IP in the range so that next_ip() starts over from the // beginning of the range. void reset() { _curr = _base; } bool range_exhausted() const { return _curr == _last; } }; IPRangeIterator(const ipv4_net&) -> IPRangeIterator; IPRangeIterator(const ipv6_net&) -> IPRangeIterator; using IPv4RangeIterator = IPRangeIterator; using IPv6RangeIterator = IPRangeIterator; // Finds a private IP /N range that does not overlap with any ranges in `excluding`. Returns // the ipv{4,6}_net with ip set to the first usable address in the range (i.e. the ".1" or "::1" // for an IPv4 mask_size of 24 or smaller). Returns nullopt if no suitable range can be found. std::optional find_private_ipv4_net(const std::vector& excluding, uint8_t mask_size); std::optional find_private_ipv6_net(const std::vector& excluding, uint8_t mask_size); } // namespace llarp ================================================ FILE: llarp/address/map.hpp ================================================ #pragma once #include "address.hpp" #include #include namespace llarp { template struct address_map { protected: std::unordered_map _local_to_remote; std::unordered_map _remote_to_local; std::unordered_map _name_to_remote; using Lock_t = util::NullLock; mutable util::NullMutex addr_mutex; public: /** This functions exactly as std::unordered_map's ::insert_or_assign method. If a key equivalent to `local` or `remote` already exists, then they will be assigned to the corresponding value. Otherwise, the values will be inserted. The returned `bool` is true if the insertion took place and `false` if assignment occurred. */ bool insert_or_assign(const LocalAddrT& local, const NetworkAddress& remote) { Lock_t l{addr_mutex}; auto [_1, ins1] = _local_to_remote.insert_or_assign(local, remote); auto [_2, ins2] = _remote_to_local.insert_or_assign(remote, local); auto [_3, ins3] = _name_to_remote.insert_or_assign(remote.name(), remote); return ins1 && ins2 && ins3; } std::optional get_remote(const LocalAddrT& local) const { Lock_t l{addr_mutex}; if (auto itr = _local_to_remote.find(local); itr != _local_to_remote.end()) return itr->second; return std::nullopt; } std::optional get_remote(const std::string& name) const { Lock_t l{addr_mutex}; if (auto itr = _name_to_remote.find(name); itr != _name_to_remote.end()) return itr->second; return std::nullopt; } std::optional operator[](const LocalAddrT& local) const { return get_remote(local); } std::optional get_local(const NetworkAddress& remote) const { Lock_t l{addr_mutex}; if (auto itr = _remote_to_local.find(remote); itr != _remote_to_local.end()) return itr->second; return std::nullopt; } std::optional operator[](const NetworkAddress& remote) const { return get_local(remote); } std::optional get_local(const std::string& name) const { Lock_t l{addr_mutex}; if (auto itr = _name_to_remote.find(name); itr != _name_to_remote.end()) return get_local(itr->second); return std::nullopt; } bool has_local(const LocalAddrT& local) const { Lock_t l{addr_mutex}; return _local_to_remote.contains(local); } bool has_remote(const NetworkAddress& remote) const { Lock_t l{addr_mutex}; return _remote_to_local.contains(remote); } void unmap(const NetworkAddress& remote) { Lock_t l{addr_mutex}; if (auto it = _remote_to_local.find(remote); it != _remote_to_local.end()) { _local_to_remote.erase(it->second); _remote_to_local.erase(it); } _name_to_remote.erase(remote.name()); } void unmap(const LocalAddrT& local) { Lock_t l{addr_mutex}; if (auto it_a = _local_to_remote.find(local); it_a != _local_to_remote.end()) { if (auto it_b = _remote_to_local.find(it_a->second); it_b != _remote_to_local.end()) { _name_to_remote.erase(it_b->first.name()); _remote_to_local.erase(it_b); } _local_to_remote.erase(it_a); } } void unmap(const std::string& name) { Lock_t l{addr_mutex}; if (auto it_a = _name_to_remote.find(name); it_a != _name_to_remote.end()) { if (auto it_b = _remote_to_local.find(it_a->second); it_b != _remote_to_local.end()) { _local_to_remote.erase(it_b->second); _remote_to_local.erase(it_b); } _name_to_remote.erase(it_a); } } }; } // namespace llarp ================================================ FILE: llarp/address/types.hpp ================================================ #pragma once #include #include namespace llarp { namespace quic = oxen::quic; using quic::ipv4; using quic::ipv4_net; using quic::ipv4_range; using quic::ipv6; using quic::ipv6_net; using quic::ipv6_range; // Used for hash combining, below inline constexpr size_t inverse_golden_ratio = sizeof(size_t) >= 8 ? 0x9e37'79b9'7f4a'7c15 : 0x9e37'79b9; } // namespace llarp template <> struct std::hash { size_t operator()(const llarp::ipv4& obj) const noexcept { return hash{}(obj.addr); } }; template <> struct std::hash { size_t operator()(const llarp::ipv6& obj) const noexcept { std::hash subhash{}; auto h = subhash(obj.hi); h ^= subhash(obj.lo) + llarp::inverse_golden_ratio + (h << 6) + (h >> 2); return h; } }; ================================================ FILE: llarp/address/utils.cpp ================================================ #include "utils.hpp" #include #include #include #include #include namespace llarp { static auto logcat = log::Cat("address-utils"); namespace detail { std::optional parse_addr_string(std::string_view arg, std::string_view tld) { std::optional ret = std::nullopt; if (auto pos = arg.find_first_of('.'); pos != std::string_view::npos) { auto _prefix = arg.substr(0, pos); // check the pubkey prefix is the right length if (_prefix.length() != PUBKEYSIZE) return ret; // verify the tld is allowed auto _tld = arg.substr(pos); if (_tld == tld and TLD::allowed(_tld)) ret = _prefix; } return ret; }; std::pair parse_addr(std::string_view addr, std::optional default_port) { std::pair result; auto &[host, port] = result; if (auto p = addr.rfind(':'); p != std::string_view::npos) { if (!parse_int(addr.substr(p + 1), port)) throw std::invalid_argument{"Invalid address: could not parse port"}; addr.remove_suffix(addr.size() - p); } else if (default_port.has_value()) // use ::has_value() in case default_port is set but is == 0 { port = *default_port; } else throw std::invalid_argument{ "Invalid address: argument contains no port and no default was specified (input:{})"_format(addr)}; bool had_sq_brackets = false; if (!addr.empty() && addr.front() == '[' && addr.back() == ']') { addr.remove_prefix(1); addr.remove_suffix(1); had_sq_brackets = true; } if (auto p = addr.find_first_not_of("0123456789."sv); p != std::string_view::npos) { if (auto q = addr.find_first_not_of("0123456789ABCDEFabcdef:."); q != std::string_view::npos) throw std::invalid_argument{"Invalid address: does not look like IPv4 or IPv6!"}; if (!had_sq_brackets) throw std::invalid_argument{"Invalid address: IPv6 addresses require [...] square brackets"}; } host = addr; return result; } } // namespace detail } // namespace llarp ================================================ FILE: llarp/address/utils.hpp ================================================ #pragma once #include #include #include namespace llarp { using namespace std::literals; namespace TLD { inline constexpr auto SNODE = ".snode"sv; inline constexpr auto LOKI = ".loki"sv; inline constexpr bool allowed(std::string_view dot_tld) { return dot_tld == SNODE || dot_tld == LOKI; } } // namespace TLD namespace detail { std::optional parse_addr_string(std::string_view arg, std::string_view tld); std::pair parse_addr(std::string_view addr, std::optional default_port); } // namespace detail } // namespace llarp ================================================ FILE: llarp/android/ifaddrs.c ================================================ /* Copyright (c) 2013, Kenneth MacKay All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "ifaddrs.h" #include #include #include #include #include #include #include #include #include #include #include typedef struct NetlinkList { struct NetlinkList* m_next; struct nlmsghdr* m_data; unsigned int m_size; } NetlinkList; static int netlink_socket(void) { int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (l_socket < 0) { return -1; } struct sockaddr_nl l_addr; memset(&l_addr, 0, sizeof(l_addr)); l_addr.nl_family = AF_NETLINK; if (bind(l_socket, (struct sockaddr*)&l_addr, sizeof(l_addr)) < 0) { close(l_socket); return -1; } return l_socket; } static int netlink_send(int p_socket, int p_request) { struct { struct nlmsghdr m_hdr; struct rtgenmsg m_msg; } l_data; memset(&l_data, 0, sizeof(l_data)); l_data.m_hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); l_data.m_hdr.nlmsg_type = p_request; l_data.m_hdr.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; l_data.m_hdr.nlmsg_pid = 0; l_data.m_hdr.nlmsg_seq = p_socket; l_data.m_msg.rtgen_family = AF_UNSPEC; struct sockaddr_nl l_addr; memset(&l_addr, 0, sizeof(l_addr)); l_addr.nl_family = AF_NETLINK; return (sendto(p_socket, &l_data.m_hdr, l_data.m_hdr.nlmsg_len, 0, (struct sockaddr*)&l_addr, sizeof(l_addr))); } static int netlink_recv(int p_socket, void* p_buffer, size_t p_len) { struct msghdr l_msg; struct iovec l_iov = {p_buffer, p_len}; struct sockaddr_nl l_addr; for (;;) { l_msg.msg_name = (void*)&l_addr; l_msg.msg_namelen = sizeof(l_addr); l_msg.msg_iov = &l_iov; l_msg.msg_iovlen = 1; l_msg.msg_control = NULL; l_msg.msg_controllen = 0; l_msg.msg_flags = 0; int l_result = recvmsg(p_socket, &l_msg, 0); if (l_result < 0) { if (errno == EINTR) { continue; } return -2; } if (l_msg.msg_flags & MSG_TRUNC) { // buffer was too small return -1; } return l_result; } } static struct nlmsghdr* getNetlinkResponse(int p_socket, int* p_size, int* p_done) { size_t l_size = 4096; void* l_buffer = NULL; for (;;) { free(l_buffer); l_buffer = malloc(l_size); if (l_buffer == NULL) { return NULL; } int l_read = netlink_recv(p_socket, l_buffer, l_size); *p_size = l_read; if (l_read == -2) { free(l_buffer); return NULL; } if (l_read >= 0) { pid_t l_pid = getpid(); struct nlmsghdr* l_hdr; for (l_hdr = (struct nlmsghdr*)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr*)NLMSG_NEXT(l_hdr, l_read)) { if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) { continue; } if (l_hdr->nlmsg_type == NLMSG_DONE) { *p_done = 1; break; } if (l_hdr->nlmsg_type == NLMSG_ERROR) { free(l_buffer); return NULL; } } return l_buffer; } l_size *= 2; } } static NetlinkList* newListItem(struct nlmsghdr* p_data, unsigned int p_size) { NetlinkList* l_item = malloc(sizeof(NetlinkList)); if (l_item == NULL) { return NULL; } l_item->m_next = NULL; l_item->m_data = p_data; l_item->m_size = p_size; return l_item; } static void freeResultList(NetlinkList* p_list) { NetlinkList* l_cur; while (p_list) { l_cur = p_list; p_list = p_list->m_next; free(l_cur->m_data); free(l_cur); } } static NetlinkList* getResultList(int p_socket, int p_request) { if (netlink_send(p_socket, p_request) < 0) { return NULL; } NetlinkList* l_list = NULL; NetlinkList* l_end = NULL; int l_size; int l_done = 0; while (!l_done) { struct nlmsghdr* l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); if (!l_hdr) { // error freeResultList(l_list); return NULL; } NetlinkList* l_item = newListItem(l_hdr, l_size); if (!l_item) { freeResultList(l_list); return NULL; } if (!l_list) { l_list = l_item; } else { l_end->m_next = l_item; } l_end = l_item; } return l_list; } static size_t maxSize(size_t a, size_t b) { return (a > b ? a : b); } static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) { switch (p_family) { case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); case AF_PACKET: return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); default: return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); } } static void makeSockaddr(sa_family_t p_family, struct sockaddr* p_dest, void* p_data, size_t p_size) { switch (p_family) { case AF_INET: memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); break; case AF_INET6: memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); break; case AF_PACKET: memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; break; default: memcpy(p_dest->sa_data, p_data, p_size); break; } p_dest->sa_family = p_family; } static void addToEnd(struct ifaddrs** p_resultList, struct ifaddrs* p_entry) { if (!*p_resultList) { *p_resultList = p_entry; } else { struct ifaddrs* l_cur = *p_resultList; while (l_cur->ifa_next) { l_cur = l_cur->ifa_next; } l_cur->ifa_next = p_entry; } } static int interpretLink(struct nlmsghdr* p_hdr, struct ifaddrs** p_resultList) { struct ifinfomsg* l_info = (struct ifinfomsg*)NLMSG_DATA(p_hdr); size_t l_nameSize = 0; size_t l_addrSize = 0; size_t l_dataSize = 0; size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); struct rtattr* l_rta; for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { void* l_rtaData = RTA_DATA(l_rta); (void)l_rtaData; size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); switch (l_rta->rta_type) { case IFLA_ADDRESS: case IFLA_BROADCAST: l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); break; case IFLA_IFNAME: l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); break; case IFLA_STATS: l_dataSize += NLMSG_ALIGN(l_rtaSize); break; default: break; } } struct ifaddrs* l_entry = malloc(sizeof(struct ifaddrs) + sizeof(int) + l_nameSize + l_addrSize + l_dataSize); if (l_entry == NULL) { return -1; } memset(l_entry, 0, sizeof(struct ifaddrs)); l_entry->ifa_name = ""; char* l_index = ((char*)l_entry) + sizeof(struct ifaddrs); char* l_name = l_index + sizeof(int); char* l_addr = l_name + l_nameSize; char* l_data = l_addr + l_addrSize; // save the interface index so we can look it up when handling the addresses. memcpy(l_index, &l_info->ifi_index, sizeof(int)); l_entry->ifa_flags = l_info->ifi_flags; l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); for (l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { void* l_rtaData = RTA_DATA(l_rta); size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); switch (l_rta->rta_type) { case IFLA_ADDRESS: case IFLA_BROADCAST: { size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); makeSockaddr(AF_PACKET, (struct sockaddr*)l_addr, l_rtaData, l_rtaDataSize); ((struct sockaddr_ll*)l_addr)->sll_ifindex = l_info->ifi_index; ((struct sockaddr_ll*)l_addr)->sll_hatype = l_info->ifi_type; if (l_rta->rta_type == IFLA_ADDRESS) { l_entry->ifa_addr = (struct sockaddr*)l_addr; } else { l_entry->ifa_broadaddr = (struct sockaddr*)l_addr; } l_addr += NLMSG_ALIGN(l_addrLen); break; } case IFLA_IFNAME: strncpy(l_name, l_rtaData, l_rtaDataSize); l_name[l_rtaDataSize] = '\0'; l_entry->ifa_name = l_name; break; case IFLA_STATS: memcpy(l_data, l_rtaData, l_rtaDataSize); l_entry->ifa_data = l_data; break; default: break; } } addToEnd(p_resultList, l_entry); return 0; } static struct ifaddrs* findInterface(int p_index, struct ifaddrs** p_links, int p_numLinks) { int l_num = 0; struct ifaddrs* l_cur = *p_links; while (l_cur && l_num < p_numLinks) { char* l_indexPtr = ((char*)l_cur) + sizeof(struct ifaddrs); int l_index; memcpy(&l_index, l_indexPtr, sizeof(int)); if (l_index == p_index) { return l_cur; } l_cur = l_cur->ifa_next; ++l_num; } return NULL; } static int interpretAddr(struct nlmsghdr* p_hdr, struct ifaddrs** p_resultList, int p_numLinks) { struct ifaddrmsg* l_info = (struct ifaddrmsg*)NLMSG_DATA(p_hdr); struct ifaddrs* l_interface = findInterface(l_info->ifa_index, p_resultList, p_numLinks); if (l_info->ifa_family == AF_PACKET) { return 0; } size_t l_nameSize = 0; size_t l_addrSize = 0; int l_addedNetmask = 0; size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); struct rtattr* l_rta; for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { void* l_rtaData = RTA_DATA(l_rta); (void)l_rtaData; size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); switch (l_rta->rta_type) { case IFA_ADDRESS: case IFA_LOCAL: if ((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) { // make room for netmask l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); l_addedNetmask = 1; } case IFA_BROADCAST: l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); break; case IFA_LABEL: l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); break; default: break; } } struct ifaddrs* l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); if (l_entry == NULL) { return -1; } memset(l_entry, 0, sizeof(struct ifaddrs)); l_entry->ifa_name = (l_interface ? l_interface->ifa_name : ""); char* l_name = ((char*)l_entry) + sizeof(struct ifaddrs); char* l_addr = l_name + l_nameSize; l_entry->ifa_flags = l_info->ifa_flags; if (l_interface) { l_entry->ifa_flags |= l_interface->ifa_flags; } l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); for (l_rta = IFA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) { void* l_rtaData = RTA_DATA(l_rta); size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); switch (l_rta->rta_type) { case IFA_ADDRESS: case IFA_BROADCAST: case IFA_LOCAL: { size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); makeSockaddr(l_info->ifa_family, (struct sockaddr*)l_addr, l_rtaData, l_rtaDataSize); if (l_info->ifa_family == AF_INET6) { if (IN6_IS_ADDR_LINKLOCAL((struct in6_addr*)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr*)l_rtaData)) { ((struct sockaddr_in6*)l_addr)->sin6_scope_id = l_info->ifa_index; } } if (l_rta->rta_type == IFA_ADDRESS) { // apparently in a point-to-point network IFA_ADDRESS contains the // dest address and IFA_LOCAL contains the local address if (l_entry->ifa_addr) { l_entry->ifa_dstaddr = (struct sockaddr*)l_addr; } else { l_entry->ifa_addr = (struct sockaddr*)l_addr; } } else if (l_rta->rta_type == IFA_LOCAL) { if (l_entry->ifa_addr) { l_entry->ifa_dstaddr = l_entry->ifa_addr; } l_entry->ifa_addr = (struct sockaddr*)l_addr; } else { l_entry->ifa_broadaddr = (struct sockaddr*)l_addr; } l_addr += NLMSG_ALIGN(l_addrLen); break; } case IFA_LABEL: strncpy(l_name, l_rtaData, l_rtaDataSize); l_name[l_rtaDataSize] = '\0'; l_entry->ifa_name = l_name; break; default: break; } } if (l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) { unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); char l_mask[16] = {0}; unsigned i; for (i = 0; i < (l_prefix / 8); ++i) { l_mask[i] = 0xff; } if (l_prefix % 8) { l_mask[i] = 0xff << (8 - (l_prefix % 8)); } makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr*)l_addr, l_mask, l_maxPrefix / 8); l_entry->ifa_netmask = (struct sockaddr*)l_addr; } addToEnd(p_resultList, l_entry); return 0; } static int interpretLinks(int p_socket, NetlinkList* p_netlinkList, struct ifaddrs** p_resultList) { int l_numLinks = 0; pid_t l_pid = getpid(); for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next) { unsigned int l_nlsize = p_netlinkList->m_size; struct nlmsghdr* l_hdr; for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) { if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) { continue; } if (l_hdr->nlmsg_type == NLMSG_DONE) { break; } if (l_hdr->nlmsg_type == RTM_NEWLINK) { if (interpretLink(l_hdr, p_resultList) == -1) { return -1; } ++l_numLinks; } } } return l_numLinks; } static int interpretAddrs(int p_socket, NetlinkList* p_netlinkList, struct ifaddrs** p_resultList, int p_numLinks) { pid_t l_pid = getpid(); for (; p_netlinkList; p_netlinkList = p_netlinkList->m_next) { unsigned int l_nlsize = p_netlinkList->m_size; struct nlmsghdr* l_hdr; for (l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) { if ((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) { continue; } if (l_hdr->nlmsg_type == NLMSG_DONE) { break; } if (l_hdr->nlmsg_type == RTM_NEWADDR) { if (interpretAddr(l_hdr, p_resultList, p_numLinks) == -1) { return -1; } } } } return 0; } int getifaddrs(struct ifaddrs** ifap) { if (!ifap) { return -1; } *ifap = NULL; int l_socket = netlink_socket(); if (l_socket < 0) { return -1; } NetlinkList* l_linkResults = getResultList(l_socket, RTM_GETLINK); if (!l_linkResults) { close(l_socket); return -1; } NetlinkList* l_addrResults = getResultList(l_socket, RTM_GETADDR); if (!l_addrResults) { close(l_socket); freeResultList(l_linkResults); return -1; } int l_result = 0; int l_numLinks = interpretLinks(l_socket, l_linkResults, ifap); if (l_numLinks == -1 || interpretAddrs(l_socket, l_addrResults, ifap, l_numLinks) == -1) { l_result = -1; } freeResultList(l_linkResults); freeResultList(l_addrResults); close(l_socket); return l_result; } void freeifaddrs(struct ifaddrs* ifa) { struct ifaddrs* l_cur; while (ifa) { l_cur = ifa; ifa = ifa->ifa_next; free(l_cur); } } ================================================ FILE: llarp/android/ifaddrs.h ================================================ /* * Copyright (c) 1995, 1999 * Berkeley Software Design, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp */ #pragma once struct ifaddrs { struct ifaddrs* ifa_next; char* ifa_name; unsigned int ifa_flags; struct sockaddr* ifa_addr; struct sockaddr* ifa_netmask; struct sockaddr* ifa_dstaddr; void* ifa_data; }; /* * This may have been defined in . Note that if is * to be included it must be included before this header file. */ #ifndef ifa_broadaddr #define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ #endif #include __BEGIN_DECLS extern int getifaddrs(struct ifaddrs** ifap); extern void freeifaddrs(struct ifaddrs* ifa); __END_DECLS ================================================ FILE: llarp/app.xml ================================================ ================================================ FILE: llarp/apple/CMakeLists.txt ================================================ # 3.13+ so that we can add link libraries to parent targets cmake_minimum_required(VERSION 3.13) if (LOKINET_DAEMON AND (BUILD_SHARED_LIBS OR NOT BUILD_STATIC_DEPS OR NOT STATIC_LINK)) message(FATAL_ERROR "macOS full builds require a full static build; perhaps use the contrib/mac.sh script to build?") endif() # god (steve jobs) made apple so that man may suffer find_library(FOUNDATION Foundation REQUIRED) target_link_libraries(lokinet-base INTERFACE ${FOUNDATION}) if (LOKINET_DAEMON) target_sources(lokinet-platform PRIVATE vpn_platform.cpp vpn_interface.cpp route_manager.cpp context_wrapper.cpp) find_library(NETEXT NetworkExtension REQUIRED) find_library(COREFOUNDATION CoreFoundation REQUIRED) add_executable(lokinet-extension MACOSX_BUNDLE PacketTunnelProvider.m DNSTrampoline.m ) enable_lto(lokinet-extension) # -fobjc-arc enables automatic reference counting for objective-C code # -e _NSExtensionMain because the appex has that instead of a `main` function entry point, of course. target_compile_options(lokinet-extension PRIVATE -fobjc-arc) if(MACOS_SYSTEM_EXTENSION) target_compile_definitions(lokinet-extension PRIVATE MACOS_SYSTEM_EXTENSION) target_compile_definitions(lokinet-base INTERFACE MACOS_SYSTEM_EXTENSION) else() target_link_options(lokinet-extension PRIVATE -e _NSExtensionMain) endif() if(MACOS_SYSTEM_EXTENSION) set(bundle_ext systemextension) set(product_type com.apple.product-type.system-extension) else() set(bundle_ext appex) set(product_type com.apple.product-type.app-extension) endif() target_link_libraries(lokinet-extension PRIVATE lokinet-core ${COREFOUNDATION} ${NETEXT}) set_target_properties(lokinet-extension PROPERTIES BUNDLE TRUE BUNDLE_EXTENSION ${bundle_ext} OUTPUT_NAME org.lokinet.network-extension MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/contrib/macos/lokinet-extension.Info.plist.in XCODE_PRODUCT_TYPE ${product_type} ) if(CODESIGN AND CODESIGN_EXT_PROFILE) add_custom_command(TARGET lokinet-extension POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CODESIGN_EXT_PROFILE} $/Contents/embedded.provisionprofile ) endif() endif() ================================================ FILE: llarp/apple/DNSTrampoline.h ================================================ #pragma once #include #include extern NSString* error_domain; /** * "Trampoline" class that listens for UDP DNS packets when we have exit mode enabled. These arrive * on localhost:1053 coming from lokinet's embedded libunbound (when exit mode is enabled), wraps * them via NetworkExtension's crappy UDP API, then sends responses back to libunbound to be * parsed/etc. This class knows nothing about DNS, it is basically just a UDP packet forwarder, but * using Apple magic reinvented wheel wrappers that are oh so wonderful like everything Apple. * * So for a lokinet configuration of "upstream=1.1.1.1", when exit mode is OFF: * - DNS requests go unbound either to 127.0.0.1:53 directly (system extension) or bounced through * TUNNELIP:53 (app extension), which forwards them (directly) to the upstream DNS server(s). * With exit mode ON: * - DNS requests go to unbound, as above, and unbound forwards them to 127.0.0.1:1053, which * encapsulates them in Apple's god awful crap, then (on a response) sends them back to * libunbound to be delivered back to the requestor. * (This assumes a non-lokinet DNS; .loki and .snode get handled before either of these). */ @interface LLARPDNSTrampoline : NSObject { // The socket libunbound talks with: uv_udp_t request_socket; // The reply address. This is a bit hacky: we configure libunbound to just use single address // (rather than a range) so that we don't have to worry about tracking different reply // addresses. @public struct sockaddr reply_addr; // UDP "session" aimed at the upstream DNS @public NWUDPSession* upstream; // Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't // callable again until the previous write finishes. Deal with this garbage API by queuing // everything than using a uv_async to process the queue. @public int write_ready; @public NSMutableArray* pending_writes; uv_async_t write_trigger; } - (void)startWithUpstreamDns:(NWUDPSession*)dns listenIp:(NSString*)listenIp listenPort:(uint16_t)listenPort uvLoop:(uv_loop_t*)loop completionHandler:(void (^)(NSError* error))completionHandler; - (void)flushWrites; - (void)dealloc; @end ================================================ FILE: llarp/apple/DNSTrampoline.m ================================================ #include "DNSTrampoline.h" #include NSString* error_domain = @"org.lokinet"; // Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv // event loop. static void on_request( uv_udp_t* socket, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { (void)flags; if (nread < 0) { NSLog(@"Read error: %s", uv_strerror(nread)); free(buf->base); return; } if (nread == 0 || !addr) { if (buf) free(buf->base); return; } LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)socket->data; // We configure libunbound to use just one single port so we'll just send replies to the last // port to talk to us. (And we're only listening on localhost in the first place). t->reply_addr = *addr; // NSData takes care of calling free(buf->base) for us with this constructor: [t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]]; [t flushWrites]; } static void on_sent(uv_udp_send_t* req, int status) { (void)status; NSArray* datagrams = (__bridge_transfer NSArray*)req->data; (void)datagrams; free(req); } // NB: called from the libuv event loop (so we don't have to worry about the above and this one // running at once from different threads). static void write_flusher(uv_async_t* async) { LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)async->data; if (t->pending_writes.count == 0) return; NSArray* data = [NSArray arrayWithArray:t->pending_writes]; [t->pending_writes removeAllObjects]; __weak LLARPDNSTrampoline* weakSelf = t; [t->upstream writeMultipleDatagrams:data completionHandler:^(NSError* error) { if (error) NSLog(@"Failed to send request to upstream DNS: %@", error); // Trigger another flush in case anything built up while Apple was doing its // things. Just call it unconditionally (rather than checking the queue) // because this handler is probably running in some other thread. [weakSelf flushWrites]; }]; } static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { (void)handle; buf->base = malloc(suggested_size); buf->len = suggested_size; } @implementation LLARPDNSTrampoline - (void)startWithUpstreamDns:(NWUDPSession*)dns listenIp:(NSString*)listenIp listenPort:(uint16_t)listenPort uvLoop:(uv_loop_t*)loop completionHandler:(void (^)(NSError* error))completionHandler { NSLog(@"Setting up trampoline"); pending_writes = [[NSMutableArray alloc] init]; write_trigger.data = (__bridge void*)self; uv_async_init(loop, &write_trigger, write_flusher); request_socket.data = (__bridge void*)self; uv_udp_init(loop, &request_socket); struct sockaddr_in recv_addr; uv_ip4_addr(listenIp.UTF8String, listenPort, &recv_addr); int ret = uv_udp_bind(&request_socket, (const struct sockaddr*)&recv_addr, UV_UDP_REUSEADDR); if (ret < 0) { NSString* errstr = [NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)]; NSError* err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}]; NSLog(@"%@", err); return completionHandler(err); } uv_udp_recv_start(&request_socket, alloc_buffer, on_request); NSLog(@"Starting DNS trampoline"); upstream = dns; __weak LLARPDNSTrampoline* weakSelf = self; [upstream setReadHandler:^(NSArray* datagrams, NSError* error) { // Reading a reply back from the UDP socket used to talk to upstream if (error) { NSLog(@"Reader handler failed: %@", error); return; } LLARPDNSTrampoline* strongSelf = weakSelf; if (!strongSelf || datagrams.count == 0) return; uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t)); size_t buf_count = 0; for (NSData* packet in datagrams) { buffers[buf_count].base = (void*)packet.bytes; buffers[buf_count].len = packet.length; buf_count++; } uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t)); uvsend->data = (__bridge_retained void*)datagrams; int ret = uv_udp_send(uvsend, &strongSelf->request_socket, buffers, buf_count, &strongSelf->reply_addr, on_sent); free(buffers); if (ret < 0) NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret)); } maxDatagrams:NSUIntegerMax]; completionHandler(nil); } - (void)flushWrites { uv_async_send(&write_trigger); } - (void)dealloc { NSLog(@"Stopping DNS trampoline"); uv_close((uv_handle_t*)&request_socket, NULL); uv_close((uv_handle_t*)&write_trigger, NULL); } @end ================================================ FILE: llarp/apple/PacketTunnelProvider.m ================================================ #include "context_wrapper.h" #include "DNSTrampoline.h" #include #include #define LLARP_APPLE_PACKET_BUF_SIZE 64 @interface LLARPPacketTunnel : NEPacketTunnelProvider { void* lokinet; llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE]; @public NEPacketTunnelNetworkSettings* settings; @public NEIPv4Route* tun_route4; @public NEIPv6Route* tun_route6; LLARPDNSTrampoline* dns_tramp; } - (void)startTunnelWithOptions:(NSDictionary*)options completionHandler:(void (^)(NSError* error))completionHandler; - (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler; - (void)handleAppMessage:(NSData*)messageData completionHandler:(void (^)(NSData* responseData))completionHandler; - (void)readPackets; - (void)updateNetworkSettings; @end static void nslogger(const char* msg) { NSLog(@"%s", msg); } static void packet_writer(int af, const void* data, size_t size, void* ctx) { if (ctx == nil || data == nil) return; NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO]; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; [t.packetFlow writePackets:@[buf] withProtocols:@[[NSNumber numberWithInt:af]]]; } static void start_packet_reader(void* ctx) { if (ctx == nil) return; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; [t readPackets]; } static void add_ipv4_route(const char* addr, const char* netmask, void* ctx) { NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask); NEIPv4Route* route = [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] subnetMask:[NSString stringWithUTF8String:netmask]]; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes) if ([r.destinationAddress isEqualToString:route.destinationAddress] && [r.destinationSubnetMask isEqualToString:route.destinationSubnetMask]) return; // Already in the settings, nothing to add. t->settings.IPv4Settings.includedRoutes = [t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route]; [t updateNetworkSettings]; } static void del_ipv4_route(const char* addr, const char* netmask, void* ctx) { NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask); NEIPv4Route* route = [[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] subnetMask:[NSString stringWithUTF8String:netmask]]; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; NSMutableArray* routes = [NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes]; for (size_t i = 0; i < routes.count; i++) { if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && [routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask]) { [routes removeObjectAtIndex:i]; i--; } } if (routes.count != t->settings.IPv4Settings.includedRoutes.count) { t->settings.IPv4Settings.includedRoutes = routes; [t updateNetworkSettings]; } } static void add_ipv6_route(const char* addr, int prefix, void* ctx) { NEIPv6Route* route = [[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] networkPrefixLength:[NSNumber numberWithInt:prefix]]; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes) if ([r.destinationAddress isEqualToString:route.destinationAddress] && [r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) return; // Already in the settings, nothing to add. t->settings.IPv6Settings.includedRoutes = [t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route]; [t updateNetworkSettings]; } static void del_ipv6_route(const char* addr, int prefix, void* ctx) { NEIPv6Route* route = [[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr] networkPrefixLength:[NSNumber numberWithInt:prefix]]; LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; NSMutableArray* routes = [NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes]; for (size_t i = 0; i < routes.count; i++) { if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] && [routes[i].destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength]) { [routes removeObjectAtIndex:i]; i--; } } if (routes.count != t->settings.IPv6Settings.includedRoutes.count) { t->settings.IPv6Settings.includedRoutes = routes; [t updateNetworkSettings]; } } static void add_default_route(void* ctx) { NSLog(@"Making the tunnel the default route"); LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute]; t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute]; [t updateNetworkSettings]; } static void del_default_route(void* ctx) { NSLog(@"Removing default route from tunnel"); LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx; t->settings.IPv4Settings.includedRoutes = @[t->tun_route4]; t->settings.IPv6Settings.includedRoutes = @[t->tun_route6]; [t updateNetworkSettings]; } @implementation LLARPPacketTunnel - (void)readPackets { [self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray* packets) { if (lokinet == nil) return; size_t size = 0; for (NEPacket* p in packets) { packet_buf[size].bytes = p.data.bytes; packet_buf[size].size = p.data.length; size++; if (size >= LLARP_APPLE_PACKET_BUF_SIZE) { llarp_apple_incoming(lokinet, packet_buf, size); size = 0; } } if (size > 0) llarp_apple_incoming(lokinet, packet_buf, size); [self readPackets]; }]; } - (void)startTunnelWithOptions:(NSDictionary*)options completionHandler:(void (^)(NSError*))completionHandler { NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap" ofType:@"signed"]; NSString* home = NSHomeDirectory(); llarp_apple_config conf = { .config_dir = home.UTF8String, .default_bootstrap = default_bootstrap.UTF8String, .ns_logger = nslogger, .packet_writer = packet_writer, .start_reading = start_packet_reader, .route_callbacks = {.add_ipv4_route = add_ipv4_route, .del_ipv4_route = del_ipv4_route, .add_ipv6_route = add_ipv6_route, .del_ipv6_route = del_ipv6_route, .add_default_route = add_default_route, .del_default_route = del_default_route}, }; lokinet = llarp_apple_init(&conf); if (!lokinet) { NSError* init_failure = [NSError errorWithDomain:error_domain code:500 userInfo:@{@"Error": @"Failed to initialize lokinet"}]; NSLog(@"%@", [init_failure localizedDescription]); return completionHandler(init_failure); } NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip]; NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask]; // We don't have a fixed address so just stick some bogus value here: settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"]; #ifdef MACOS_SYSTEM_EXTENSION NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip]; #else // TODO: placeholder NSString* dns_ip = ip; #endif NSLog(@"setting dns to %@", dns_ip); NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dns_ip]]; dns.domainName = @"localhost.loki"; dns.matchDomains = @[@""]; // In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems // highly unreliable, though: often it just doesn't work at all (perhaps only if we make // ourselves the default route?), and even when it does work, it seems there are secret reasons // that some domains (such as instagram.com) still won't work because there's some magic sauce // in the OS that Apple engineers don't want to disclose ("This is what I expected, actually. // Although I will not comment on what I believe is happening here", from // https://developer.apple.com/forums/thread/685410). // // So the documentation sucks and the feature doesn't appear to work, so as much as it would be // nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything // and use our default upstream. dns.matchDomains = @[@""]; dns.matchDomainsNoSearch = true; dns.searchDomains = @[]; settings.DNSSettings = dns; NWHostEndpoint* upstreamdns_ep; if (strlen(conf.upstream_dns)) upstreamdns_ep = [NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns] port:@(conf.upstream_dns_port).stringValue]; NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]]; tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask]; ipv4.includedRoutes = @[tun_route4]; settings.IPv4Settings = ipv4; NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip]; NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix]; NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6] networkPrefixLengths:@[ip6_prefix]]; tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6 networkPrefixLength:ip6_prefix]; ipv6.includedRoutes = @[tun_route6]; settings.IPv6Settings = ipv6; __weak LLARPPacketTunnel* weakSelf = self; [self setTunnelNetworkSettings:settings completionHandler:^(NSError* err) { if (err) { NSLog(@"Failed to configure lokinet tunnel: %@", err); return completionHandler(err); } LLARPPacketTunnel* strongSelf = weakSelf; if (!strongSelf) return completionHandler(nil); int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*)strongSelf); if (start_ret != 0) { NSError* start_failure = [NSError errorWithDomain:error_domain code:start_ret userInfo:@{@"Error": @"Failed to start lokinet"}]; NSLog(@"%@", start_failure); lokinet = nil; return completionHandler(start_failure); } NSString* dns_tramp_ip = @"127.0.0.1"; NSLog( @"Starting DNS exit mode trampoline to %@ on %@:%d", upstreamdns_ep, dns_tramp_ip, dns_trampoline_port); NWUDPSession* upstreamdns = [strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep fromEndpoint:nil]; strongSelf->dns_tramp = [LLARPDNSTrampoline alloc]; [strongSelf->dns_tramp startWithUpstreamDns:upstreamdns listenIp:dns_tramp_ip listenPort:dns_trampoline_port uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet) completionHandler:^(NSError* error) { if (error) NSLog(@"Error starting dns trampoline: %@", error); return completionHandler(error); }]; }]; } - (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler { if (lokinet) { llarp_apple_shutdown(lokinet); lokinet = nil; } completionHandler(); } - (void)handleAppMessage:(NSData*)messageData completionHandler:(void (^)(NSData* responseData))completionHandler { NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO]; completionHandler(response); } - (void)updateNetworkSettings { self.reasserting = YES; __weak LLARPPacketTunnel* weakSelf = self; // Apple documentation says that setting network settings to nil isn't required before setting // it to a new value. Apple lies: both end up with a routing table that looks exactly the same // (from both `netstat -rn` and from everything that happens in `route -n monitor`), but if we // don't call with nil first then everything fails to route to either lokinet *and* clearnet // through the exit, so there is apparently some special magic internal Apple state that // actually *does* require the tunnel settings being reset with nil first. // // Thanks for the accurate documentation, Apple. // [self setTunnelNetworkSettings:nil completionHandler:^(NSError* err) { if (err) NSLog(@"Failed to clear lokinet tunnel settings: %@", err); LLARPPacketTunnel* strongSelf = weakSelf; if (strongSelf) { [weakSelf setTunnelNetworkSettings:strongSelf->settings completionHandler:^(NSError* err) { LLARPPacketTunnel* strongSelf = weakSelf; if (strongSelf) strongSelf.reasserting = NO; if (err) NSLog( @"Failed to reconfigure lokinet tunnel settings: " @"%@", err); }]; } }]; } @end #ifdef MACOS_SYSTEM_EXTENSION int main() { [NEProvider startSystemExtensionMode]; dispatch_main(); } #endif ================================================ FILE: llarp/apple/context.hpp ================================================ #pragma once #include "route_manager.hpp" #include "vpn_platform.hpp" #include namespace llarp::apple { struct Context : public llarp::Context { std::shared_ptr make_vpn_platform() override { return std::make_shared( *this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context); } // Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the // main point of these is to get passed through to VPNInterface, which will be called during // setup, after construction. VPNInterface::packet_write_callback m_PacketWriter; VPNInterface::on_readable_callback m_OnReadable; llarp_route_callbacks route_callbacks{}; void* callback_context = nullptr; }; } // namespace llarp::apple ================================================ FILE: llarp/apple/context_wrapper.cpp ================================================ #include "context_wrapper.h" #include "context.hpp" #include "vpn_interface.hpp" #include #include #include #include #include #include #include #include #include namespace { static auto logcat = oxen::log::Cat("apple.ctx_wrapper"); struct instance_data { llarp::apple::Context context; std::thread runner; packet_writer_callback packet_writer; start_reading_callback start_reading; std::weak_ptr iface; }; } // namespace // Expose this with C linkage so that objective-c can use it extern "C" const uint16_t dns_trampoline_port = llarp::apple::dns_trampoline_port; void* llarp_apple_init(llarp_apple_config* appleconf) { llarp::log::clear_sinks(); llarp::log::add_sink(std::make_shared( [](const char* msg, void* nslog) { reinterpret_cast(nslog)(msg); }, nullptr, reinterpret_cast(appleconf->ns_logger))); llarp::logRingBuffer = std::make_shared(100); llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO); try { auto config_dir = std::filesystem::u8path(appleconf->config_dir); auto config = std::make_shared(config_dir); std::filesystem::path config_path = config_dir / "lokinet.ini"; if (!exists(config_path)) llarp::ensure_config(config_dir, config_path, false, llarp::config::Type::FullClient); config->load(config_path); // If no range is specified then go look for a free one, set that in the config, and then // return it to the caller via the char* parameters. auto& range = config->network.if_addr; if (!range.addr.h) { if (auto maybe = llarp::net::Platform::Default_ptr()->find_free_range()) range = *maybe; else throw std::runtime_error{"Could not find any free IP range"}; } auto addr = llarp::net::TruncateV6(range.addr).to_string(); auto mask = llarp::net::TruncateV6(range.netmask_bits).to_string(); if (addr.size() > 15 || mask.size() > 15) throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"}; std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip)); std::strncpy(appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask)); // TODO: in the future we want to do this properly with our pubkey (see issue #1705), but // that's going to take a bit more work because we currently can't *get* the (usually) // ephemeral pubkey at this stage of lokinet configuration. So for now we just stick our // IPv4 address into it until #1705 gets implemented. llarp::huint128_t ipv6{llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}}; std::strncpy(appleconf->tunnel_ipv6_ip, ipv6.to_string().c_str(), sizeof(appleconf->tunnel_ipv6_ip)); appleconf->tunnel_ipv6_prefix = 48; appleconf->upstream_dns[0] = '\0'; for (auto& upstream : config->dns.upstream_dns) { if (upstream.isIPv4()) { std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str()); appleconf->upstream_dns_port = upstream.getPort(); break; } } #ifdef MACOS_SYSTEM_EXTENSION std::strncpy( appleconf->dns_bind_ip, config->dns.m_bind.front().hostString().c_str(), sizeof(appleconf->dns_bind_ip)); #endif // If no explicit bootstrap then set the system default one included with the app bundle if (config->bootstrap.files.empty()) config->bootstrap.files.push_back(std::filesystem::u8path(appleconf->default_bootstrap)); auto inst = std::make_unique(); inst->context.Configure(std::move(config)); inst->context.route_callbacks = appleconf->route_callbacks; inst->packet_writer = appleconf->packet_writer; inst->start_reading = appleconf->start_reading; return inst.release(); } catch (const std::exception& e) { oxen::log::error(logcat, "Failed to initialize lokinet from config: {}", e.what()); } return nullptr; } int llarp_apple_start(void* lokinet, void* callback_context) { auto* inst = static_cast(lokinet); inst->context.callback_context = callback_context; inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) { inst->packet_writer(af_family, data, size, callback_context); return true; }; inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) { inst->iface = iface.weak_from_this(); inst->start_reading(callback_context); }; std::promise result; inst->runner = std::thread{[inst, &result] { const llarp::RuntimeOptions opts{}; try { inst->context.Setup(opts); } catch (...) { result.set_exception(std::current_exception()); return; } result.set_value(); inst->context.Run(opts); }}; try { result.get_future().get(); } catch (const std::exception& e) { oxen::log::error(logcat, "Failed to initialize lokinet: {}", e.what()); return -1; } return 0; } uv_loop_t* llarp_apple_get_uv_loop(void* lokinet) { auto& inst = *static_cast(lokinet); auto uvw = inst.context.loop->MaybeGetUVWLoop(); assert(uvw); return uvw->raw(); } int llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size) { auto& inst = *static_cast(lokinet); auto iface = inst.iface.lock(); if (!iface) return -1; int count = 0; for (size_t i = 0; i < size; i++) { llarp_buffer_t buf{static_cast(packets[i].bytes), packets[i].size}; if (iface->OfferReadPacket(buf)) count++; else oxen::log::error(logcat, "invalid IP packet: {}", llarp::buffer_printer(buf)); } iface->MaybeWakeUpperLayers(); return count; } void llarp_apple_shutdown(void* lokinet) { auto* inst = static_cast(lokinet); inst->context.CloseAsync(); inst->context.Wait(); inst->runner.join(); delete inst; } ================================================ FILE: llarp/apple/context_wrapper.h ================================================ #pragma once // C-linkage wrappers for interacting with a lokinet context, so that we can call them from Swift // code (which currently doesn't support C++ interoperability at all). #ifdef __cplusplus extern "C" { #endif #include #include #include // Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route // when in exit mode. extern const uint16_t dns_trampoline_port; /// C callback function for us to invoke when we need to write a packet typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx); /// C callback function to invoke once we are ready to start receiving packets typedef void (*start_reading_callback)(void* ctx); /// C callback that bridges things into NSLog typedef void (*ns_logger_callback)(const char* msg); /// C callbacks to add/remove specific and default routes to the tunnel typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx); typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx); typedef void (*llarp_default_route_callback)(void* ctx); typedef struct llarp_route_callbacks { /// Callback invoked to set up an IPv4 range that should be routed through the tunnel /// interface. Called with the address and netmask. llarp_route_ipv4_callback add_ipv4_route; /// Callback invoked to set the tunnel as the default IPv4 route. llarp_default_route_callback add_ipv4_default_route; /// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with /// the address and netmask. llarp_route_ipv4_callback del_ipv4_route; /// Callback invoked to set up an IPv6 range that should be routed through the tunnel /// interface. Called with the address and netmask. llarp_route_ipv6_callback add_ipv6_route; /// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with /// the address and netmask. llarp_route_ipv6_callback del_ipv6_route; /// Callback invoked to set the tunnel as the default IPv4/IPv6 route. llarp_default_route_callback add_default_route; /// Callback invoked to remove the tunnel as the default IPv4/IPv6 route. llarp_default_route_callback del_default_route; } llarp_route_callbacks; /// Pack of crap to be passed into llarp_apple_init to initialize typedef struct llarp_apple_config { /// lokinet configuration directory, expected to be the application-specific "home" /// directory, which is where state files are stored and the lokinet.ini will be loaded (or /// created if it doesn't exist). const char* config_dir; /// path to the default bootstrap.signed file included in installation, which will be used /// by default when no specific bootstrap is in the config file. const char* default_bootstrap; /// llarp_apple_init writes the IP address for the primary tunnel IP address here, /// null-terminated. char tunnel_ipv4_ip[INET_ADDRSTRLEN]; /// llarp_apple_init writes the netmask of the tunnel address here, null-terminated. char tunnel_ipv4_netmask[INET_ADDRSTRLEN]; /// Writes the IPv6 address for the tunnel here, null-terminated. char tunnel_ipv6_ip[INET6_ADDRSTRLEN]; /// IPv6 address prefix. uint16_t tunnel_ipv6_prefix; /// The first upstream DNS server's IPv4 address the OS should use when in exit mode. /// (Currently on mac in exit mode we only support querying the first such configured /// server). char upstream_dns[INET_ADDRSTRLEN]; uint16_t upstream_dns_port; #ifdef MACOS_SYSTEM_EXTENSION /// DNS bind IP; llarp_apple_init writes the lokinet config value here so that we know (in /// Apple API code) what to set DNS to when lokinet gets turned on. Null terminated. char dns_bind_ip[INET_ADDRSTRLEN]; #endif /// \defgroup callbacks Callbacks /// Callbacks we invoke for various operations that require glue into the Apple network /// extension APIs. All of these except for ns_logger are passed the pointer provided to /// llarp_apple_start when invoked. /// @{ /// simple wrapper around NSLog for lokinet message logging ns_logger_callback ns_logger; /// C function callback that will be called when we need to write a packet to the packet /// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of /// the data in bytes. packet_writer_callback packet_writer; /// C function callback that will be called when lokinet is setup and ready to start /// receiving packets from the packet tunnel. This should set up the read handler to /// deliver packets via llarp_apple_incoming. start_reading_callback start_reading; /// Callbacks invoked to add/remove routes to the tunnel. llarp_route_callbacks route_callbacks; /// @} } llarp_apple_config; /// Initializes a lokinet instance by initializing various objects and loading the configuration /// (if /lokinet.ini exists). Does not actually start lokinet (call /// llarp_apple_start for that). /// /// Returns NULL if there was a problem initializing/loading the configuration, otherwise /// returns an opaque void pointer that should be passed into the other llarp_apple_* functions. /// /// \param config pointer to a llarp_apple_config where we get the various settings needed /// and return the ip/mask/dns fields needed for the tunnel. void* llarp_apple_init(llarp_apple_config* config); /// Starts the lokinet instance in a new thread. /// /// \param lokinet the void pointer returned by llarp_apple_init /// /// \param callback_context Opaque pointer that is passed into the various callbacks provided to /// llarp_apple_init. This code does nothing with this pointer aside from passing it through to /// callbacks. /// /// \returns 0 on succesful startup, -1 on failure. int llarp_apple_start(void* lokinet, void* callback_context); /// Returns a pointer to the uv event loop. Must have called llarp_apple_start already. uv_loop_t* llarp_apple_get_uv_loop(void* lokinet); /// Struct of packet data; a C array of tests gets passed to llarp_apple_incoming typedef struct llarp_incoming_packet { const void* bytes; size_t size; } llarp_incoming_packet; /// Called to deliver one or more incoming packets from the apple layer into lokinet. Takes a C /// array of `llarp_incoming_packets` with pointers/sizes set to the individual new packets that /// have arrived. /// /// Returns the number of valid packets on success (which can be less than the number of /// provided packets, if some failed to parse), or -1 if there is no current active VPNInterface /// associated with the lokinet instance (which generally means llarp_apple_start wasn't called /// or failed, or lokinet is in the process of shutting down). int llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size); /// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to /// shut down and rejoins the thread. After this call the given pointer is no longer valid. void llarp_apple_shutdown(void* lokinet); #ifdef __cplusplus } // extern "C" #endif ================================================ FILE: llarp/apple/route_manager.cpp ================================================ #include "route_manager.hpp" #include #include #include namespace llarp::apple { static auto logcat = log::Cat("apple.route_manager"); void RouteManager::check_trampoline(bool enable) { if (trampoline_active == enable) return; auto router = context.router; if (!router) { log::error(logcat, "Cannot reconfigure to use DNS trampoline: no router"); return; } auto& tun = router->tun_endpoint(); if (!tun) { log::error(logcat, "Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)"); return; } if (enable) tun.reconfigure_dns({oxen::quic::Address{"127.0.0.1", dns_trampoline_port}}); else tun->reconfigure_dns(router->config()->dns._upstream_dns); trampoline_active = enable; } void RouteManager::add_default_route_via_interface(vpn::NetworkInterface&) { check_trampoline(true); if (callback_context and route_callbacks.add_default_route) route_callbacks.add_default_route(callback_context); } void RouteManager::delete_default_route_via_interface(vpn::NetworkInterface&) { check_trampoline(false); if (callback_context and route_callbacks.del_default_route) route_callbacks.del_default_route(callback_context); } void RouteManager::add_route_via_interface(vpn::NetworkInterface&, ipv4_range range) { check_trampoline(true); if (callback_context) { if (route_callbacks.add_ipv4_route) route_callbacks.add_ipv4_route( range.BaseAddressString().c_str(), std::string{range.mask}.c_str(), callback_context); } } void RouteManager::add_route_via_interface(vpn::NetworkInterface&, ipv6_range range) { check_trampoline(true); if (callback_context) { if (route_callbacks.add_ipv6_route) route_callbacks.add_ipv6_route(range.BaseAddressString().c_str(), range.mask, callback_context); } } void RouteManager::delete_route_via_interface(vpn::NetworkInterface&, ipv4_range range) { check_trampoline(false); if (callback_context) { if (route_callbacks.del_ipv4_route) route_callbacks.del_ipv4_route( range.ip.to_string().c_str(), std::string{range.mask}.c_str(), callback_context); } } void RouteManager::delete_route_via_interface(vpn::NetworkInterface&, ipv6_range range) { check_trampoline(false); if (callback_context) { if (route_callbacks.del_ipv6_route) route_callbacks.del_ipv6_route(range.ip.to_string().c_str(), range.mask, callback_context); } } } // namespace llarp::apple ================================================ FILE: llarp/apple/route_manager.hpp ================================================ #pragma once #include "context_wrapper.h" #include #include namespace llarp::apple { class RouteManager final : public llarp::vpn::AbstractRouteManager { public: RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context) : context{ctx}, callback_context{callback_context}, route_callbacks{std::move(rcs)} {} /// These are called for poking route holes, but we don't have to do that at all on macos /// because the appex isn't subject to its own rules. void add_route(quic::Address /*ip*/, quic::Address /*gateway*/) override {} void delete_route(quic::Address /*ip*/, quic::Address /*gateway*/) override {} void add_default_route_via_interface(vpn::NetworkInterface& vpn) override; void delete_default_route_via_interface(vpn::NetworkInterface& vpn) override; void add_route_via_interface(vpn::NetworkInterface& vpn, ipv4_range range) override; void add_route_via_interface(vpn::NetworkInterface& vpn, ipv6_range range) override; void delete_route_via_interface(vpn::NetworkInterface& vpn, ipv4_range range) override; void delete_route_via_interface(vpn::NetworkInterface& vpn, ipv6_range range) override; std::vector get_non_interface_gateways(vpn::NetworkInterface& /*vpn*/) override { // We can't get this on mac from our sandbox, but we don't actually need it because we // ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP. return std::vector{}; } private: llarp::Context& context; bool trampoline_active = false; void check_trampoline(bool enable); void* callback_context = nullptr; llarp_route_callbacks route_callbacks; }; } // namespace llarp::apple ================================================ FILE: llarp/apple/vpn_interface.cpp ================================================ #include "vpn_interface.hpp" #include "context.hpp" #include namespace llarp::apple { VPNInterface::VPNInterface( Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable, Router* router) : vpn::NetworkInterface{}, _pkt_writer{std::move(packet_writer)}, _on_readable{std::move(on_readable)}, _router{router} { ctx._loop->call_soon([this] { _on_readable(*this); }); } bool VPNInterface::OfferReadPacket(const llarp_buffer_t& buf) { IPPacket pkt; if (!pkt.load(buf.copy())) return false; _read_que.tryPushBack(std::move(pkt)); return true; } void VPNInterface::MaybeWakeUpperLayers() const { // } int VPNInterface::PollFD() const { return -1; } IPPacket VPNInterface::read_next_packet() { IPPacket pkt{}; if (not _read_que.empty()) pkt = _read_que.popFront(); return pkt; } bool VPNInterface::write_packet(IPPacket pkt) { // TODO: replace this with IPPacket::to_udp (void)pkt; // int af_family = pkt() ? AF_INET6 : AF_INET; // return _pkt_writer(af_family, pkt.data(), pkt.size()); return true; } } // namespace llarp::apple ================================================ FILE: llarp/apple/vpn_interface.hpp ================================================ #pragma once #include #include #include #include namespace llarp::apple { struct Context; class VPNInterface final : public vpn::NetworkInterface, public std::enable_shared_from_this { public: using packet_write_callback = std::function; using on_readable_callback = std::function; explicit VPNInterface( Context& ctx, packet_write_callback packet_writer, on_readable_callback on_readable, Router* router); // Method to call when a packet has arrived to deliver the packet to lokinet bool OfferReadPacket(const llarp_buffer_t& buf); int PollFD() const override; IPPacket read_next_packet() override; bool write_packet(IPPacket pkt) override; void MaybeWakeUpperLayers() const override; private: // Function for us to call when we have a packet to emit. Should return true if the packet // was handed off to the OS successfully. packet_write_callback _pkt_writer; // Called when we are ready to start reading packets on_readable_callback _on_readable; inline static constexpr auto PacketQueueSize = 1024; thread::Queue _read_que{PacketQueueSize}; Router* const _router; }; } // namespace llarp::apple ================================================ FILE: llarp/apple/vpn_platform.cpp ================================================ #include "vpn_platform.hpp" #include "context.hpp" namespace llarp::apple { VPNPlatform::VPNPlatform( Context& ctx, VPNInterface::packet_write_callback packet_writer, VPNInterface::on_readable_callback on_readable, llarp_route_callbacks route_callbacks, void* callback_context) : _context{ctx}, _route_manager{ctx, std::move(route_callbacks), callback_context}, _packet_writer{std::move(packet_writer)}, _read_cb{std::move(on_readable)} {} std::shared_ptr VPNPlatform::obtain_interface(vpn::InterfaceInfo, Router* router) { return std::make_shared(_context, _packet_writer, _read_cb, router); } } // namespace llarp::apple ================================================ FILE: llarp/apple/vpn_platform.hpp ================================================ #pragma once #include "route_manager.hpp" #include "vpn_interface.hpp" #include namespace llarp::apple { class VPNPlatform final : public vpn::Platform { public: explicit VPNPlatform( Context& ctx, VPNInterface::packet_write_callback packet_writer, VPNInterface::on_readable_callback on_readable, llarp_route_callbacks route_callbacks, void* callback_context); std::shared_ptr obtain_interface(vpn::InterfaceInfo, Router*) override; vpn::AbstractRouteManager& RouteManager() override { return _route_manager; } private: Context& _context; apple::RouteManager _route_manager; VPNInterface::packet_write_callback _packet_writer; VPNInterface::on_readable_callback _read_cb; }; } // namespace llarp::apple ================================================ FILE: llarp/auth/auth.cpp ================================================ #include "auth.hpp" #include namespace llarp::auth { static const std::unordered_map codes = { {"OKAY"sv, AuthCode::ACCEPTED}, {"REJECT"sv, AuthCode::REJECTED}, {"PAYME"sv, AuthCode::PAYMENT_REQUIRED}, {"LIMITED"sv, AuthCode::RATE_LIMIT}}; /// maybe get auth result from string std::optional parse_code(std::string_view data) { if (auto it = codes.find(data); it != codes.end()) return it->second; return std::nullopt; } static const std::unordered_map types = { {"file"sv, AuthType::FILE}, {"lmq"sv, AuthType::OMQ}, {"whitelist"sv, AuthType::WHITELIST}, {"none"sv, AuthType::NONE}}; } // namespace llarp::auth ================================================ FILE: llarp/auth/auth.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include namespace llarp { class Router; } namespace llarp::auth { /// authentication status code enum class AuthCode : uint64_t { /// explicitly accepted ACCEPTED = 0, /// explicitly rejected REJECTED = 1, /// attempt failed FAILED = 2, /// attempt rate limited RATE_LIMIT = 3, /// need mo munny PAYMENT_REQUIRED = 4 }; /// auth result object with code and reason struct AuthResult { AuthCode code; std::string reason; }; /// info needed by clients in order to authenticate to a remote endpoint struct AuthInfo { std::string token; }; /// what kind of backend to use for auth enum class AuthType { /// no authentication NONE, /// manual whitelist WHITELIST, /// OMQ server OMQ, /// static file FILE, }; struct AuthPolicy { protected: Router& _router; public: AuthPolicy(Router& r) : _router{r} {} virtual ~AuthPolicy() = default; const Router& router() const { return _router; } Router& router() { return _router; } }; /// maybe get auth result from string std::optional parse_code(std::string_view data); } // namespace llarp::auth ================================================ FILE: llarp/auth/file.cpp ================================================ #include "auth.hpp" #include #include namespace llarp::auth { AuthResult FileAuthPolicy::check_files(const AuthInfo& info) const { for (const auto& f : _files) { std::ifstream i{f}; std::string line{}; while (std::getline(i, line)) { // split off comments const auto parts = split_any(line, "#;", true); if (auto part = parts[0]; not parts.empty() and not parts[0].empty()) { // split off whitespaces and check password if (check_passwd(std::string{TrimWhitespace(part)}, info.token)) return AuthResult{AuthCode::ACCEPTED, "accepted by whitelist"}; } } } return AuthResult{AuthCode::REJECTED, "rejected by whitelist"}; } bool FileAuthPolicy::check_passwd(std::string hash, std::string challenge) const { switch (_type) { case AuthFileType::PLAIN: return hash == challenge; case AuthFileType::HASHES: #ifdef LOKINET_HAVE_CRYPT return crypto::check_passwd_hash(std::move(hash), std::move(challenge)); #else return false; #endif } return false; } } // namespace llarp::auth ================================================ FILE: llarp/auth/file.hpp ================================================ #pragma once #include "auth.hpp" #include #include #include #include #include #include namespace llarp { class Router; } namespace llarp::auth { /// how to interpret an file for auth enum class AuthFileType { PLAIN, HASHES, }; struct FileAuthPolicy final : public AuthPolicy { FileAuthPolicy(Router& r, std::vector files, AuthFileType filetype) : AuthPolicy{r}, _files{std::move(files)}, _type{filetype} {} private: const std::vector _files; const AuthFileType _type; mutable util::Mutex _m; /// returns an auth result for a auth info challange, opens every file until it finds a /// token matching it this is expected to be done in the IO thread AuthResult check_files(const AuthInfo& info) const; bool check_passwd(std::string hash, std::string challenge) const; }; } // namespace llarp::auth ================================================ FILE: llarp/auth/rpc.cpp ================================================ #include "rpc.hpp" #include #include namespace llarp::auth { static auto logcat = log::Cat("rpc.auth"); RPCAuthPolicy::RPCAuthPolicy(Router& r, std::string url, std::string method, oxenmq::OxenMQ& omq) : AuthPolicy{r}, _endpoint{std::move(url)}, _method{std::move(method)}, // _whitelist{std::move(whitelist_addrs)}, // _static_tokens{std::move(whitelist_tokens)}, _omq{omq} { if (_endpoint.empty() or _method.empty()) throw std::invalid_argument{ "RPC AuthPolicy must be initialized with an endpoint to query and a method to invoke!"}; } RPCAuthPolicy::~RPCAuthPolicy() = default; void RPCAuthPolicy::start() { _omq.connect_remote( _endpoint, [this](oxenmq::ConnectionID c) { _omq_conn = std::make_unique(std::move(c)); log::info(logcat, "OMQ connected to endpoint auth server"); }, [this](oxenmq::ConnectionID, std::string_view fail) { log::warning(logcat, "OMQ failed to connect to endpoint auth server: {}", fail); _router.loop.call_later(1s, [this] { start(); }); }); } } // namespace llarp::auth ================================================ FILE: llarp/auth/rpc.hpp ================================================ #pragma once #include "auth.hpp" #include #include #include namespace oxenmq { class OxenMQ; struct ConnectionID; } // namespace oxenmq namespace llarp::auth { struct RPCAuthPolicy final : public AuthPolicy { explicit RPCAuthPolicy(Router& r, std::string url, std::string method, oxenmq::OxenMQ& omq); ~RPCAuthPolicy() override; void start(); private: const std::string _endpoint; const std::string _method; // const std::unordered_set _whitelist; // const std::unordered_set _static_tokens; oxenmq::OxenMQ& _omq; std::unique_ptr _omq_conn; std::unordered_set _pending_sessions; }; } // namespace llarp::auth ================================================ FILE: llarp/auth/session.cpp ================================================ #include "session.hpp" #include #include namespace llarp::auth { static auto logcat = log::Cat("auth_policy"); SessionAuthPolicy::SessionAuthPolicy(Router& r, RouterID& remote, bool is_snode, bool is_exit) : AuthPolicy{r}, _is_snode_service{is_snode}, _is_exit_service{is_exit}, _remote{remote, not _is_snode_service} { // These can both be false but CANNOT both be true if (_is_exit_service and _is_snode_service) throw std::runtime_error{"Cannot create SessionAuthPolicy for a remote exit and remote service!"}; if (_is_snode_service) _session_key = _router.secret_key(); else _session_key = crypto::generate_ed25519(); } std::optional SessionAuthPolicy::fetch_auth_token() { std::optional ret = std::nullopt; auto& exit_auths = _router.config().network.exit_auths; if (auto itr = exit_auths.find(_remote); itr != exit_auths.end()) ret = itr->second; return ret; } bool SessionAuthPolicy::load_key_from_file(const char* fname) { try { KeyManager::load_from_file(_session_key, fname); return true; } catch (const std::exception& e) { log::error(logcat, "Failed to load secret key from {}: {}", fname, e.what()); } return false; } } // namespace llarp::auth ================================================ FILE: llarp/auth/session.hpp ================================================ #pragma once #include "auth.hpp" namespace llarp { class Router; } namespace llarp::auth { struct SessionAuthPolicy final : public AuthPolicy { private: const bool _is_snode_service{false}; const bool _is_exit_service{false}; Ed25519SecretKey _session_key; NetworkAddress _remote; public: SessionAuthPolicy(Router& r, RouterID& remote, bool is_snode, bool is_exit = false); bool load_key_from_file(const char* fname); std::optional fetch_auth_token(); const Ed25519SecretKey& session_key() const { return _session_key; } bool is_snode_service() const { return _is_snode_service; } bool is_exit_service() const { return _is_exit_service; } }; } // namespace llarp::auth ================================================ FILE: llarp/config/config.cpp ================================================ #include "config.hpp" #include "definition.hpp" #include "ini.hpp" #include #include #include #include #include #include #include #include #include #include #ifndef LOKINET_EMBEDDED_ONLY #include #endif namespace llarp { static auto logcat = log::Cat("config"); static bool check_path_op(std::optional& path) { if (not path.has_value()) { log::info(logcat, "Path input failed to parse..."); } else if (path->empty()) { log::warning(logcat, "Path contents ({}) empty...", path->c_str()); path.reset(); } else { log::debug(logcat, "Valid path parsed ({})", path->c_str()); return true; } return false; } using namespace config; const llarp::net::Platform* ConfigGenParameters::net_ptr() { #ifndef LOKINET_EMBEDDED_ONLY if (type != config::Type::EmbeddedClient) return llarp::net::Platform::Default_ptr(); #endif return nullptr; } static auto public_ip_loader( std::optional& into, std::string conf_name, std::string deprecated_for = ""s) { return [&into, conf_name = std::move(conf_name), deprecated_for = std::move(deprecated_for)](std::string ip) { if (!deprecated_for.empty()) log::warning(logcat, "{} is deprecated; use {} instead", conf_name, deprecated_for); try { quic::Address a{ip, into ? into->port() : uint16_t{0}}; if (!a.is_ipv4()) throw std::invalid_argument{"IP must be an IPv4 address"}; if (!a.is_public_ip()) throw std::invalid_argument{"IP is not public"}; into = std::move(a); } catch (const std::exception& e) { throw std::invalid_argument{"Invalid {}: {}"_format(conf_name, e.what())}; } }; } static auto public_port_loader( std::optional& into, std::string conf_name, std::string deprecated_for = ""s) { return [&into, conf_name = std::move(conf_name), deprecated_for = std::move(deprecated_for)](uint16_t port) { if (!deprecated_for.empty()) log::warning(logcat, "{} is deprecated; use {} instead", conf_name, deprecated_for); if (port == 0) throw std::invalid_argument{"{} cannot be 0"_format(conf_name)}; if (!into) into.emplace(); into->set_port(port); }; } void RouterConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) { conf.add_section_comments( "router", { "Configuration for routing activity.", }); conf.define_option("router", "job-queue-size", Default{1024 * 8}, Hidden, [this](int arg) { if (arg < 1024) throw std::invalid_argument("job-queue-size must be 1024 or greater"); job_que_size = arg; }); conf.define_option( "router", "netid", Default{"{}"_format(NetID::MAINNET)}, Comment{"Network ID; this is '{}' for mainnet, '{}' for testnet."_format(NetID::MAINNET, NetID::TESTNET)}, [this](std::string arg) { net_id = netid_from_string(arg); }); conf.define_option("router", "relay-connections", Deprecated); conf.define_option("router", "min-connections", Deprecated); conf.define_option("router", "max-connections", Deprecated); conf.define_option("router", "nickname", Deprecated); conf.define_option( "router", "data-dir", Default{params.default_data_dir}, Comment{ "Optional directory for containing lokinet runtime data. This includes generated", "private keys.", }, [this](std::filesystem::path arg) { if (arg.empty()) arg = std::filesystem::path{"."}; if (not exists(arg)) throw std::runtime_error{"Specified [router]:data-dir {} does not exist"_format(arg)}; data_dir = std::move(arg); }); conf.define_option( "router", "public-ip", RelayOnly, Comment{ "For complex network configurations where the detected IP is incorrect or non-public", "this setting specifies the public IPv4 address at which this router reachable.", }, public_ip_loader(public_addr, "[router]:public-address")); conf.define_option("router", "public-address", Hidden, [](std::string) { throw std::invalid_argument{ "[router]:public-address option no longer supported, use [router]:public-ip and " "[router]:public-port instead"}; }); conf.define_option( "router", "public-port", RelayOnly, Comment{ "When specifying public-ip=, this specifies the public UDP port at which this lokinet", "router is reachable. Defaults to the [bind]:listen port when public-ip is specified.", }, public_port_loader(public_addr, "[router]:public-port")); conf.add_options_validator([this] { if (public_addr and not public_addr->is_public_ip()) throw std::invalid_argument{"[router]:public-ip is required when specifying [router]:public-port"}; }); // FIXME: this option isn't currently used! conf.define_option( "router", "worker-threads", Default{0}, Comment{ "The number of threads available for performing cryptographic functions.", "The minimum is one thread, but network performance may increase with more.", "threads. Should not exceed the number of logical CPU cores.", "0 means use the number of logical CPU cores detected at startup.", }, [this](int arg) { if (arg < 0) throw std::invalid_argument("worker-threads must be >= 0"); worker_threads = arg; }); // Hidden option because this isn't something that should ever be turned off occasionally // when doing dev/testing work. conf.define_option("router", "block-bogons", Default{true}, Hidden, assignment_acceptor(block_bogons)); conf.define_option("router", "contact-file", Deprecated); conf.define_option("router", "encryption-privkey", Deprecated); conf.define_option("router", "ident-privkey", Deprecated); conf.define_option("router", "transport-privkey", RelayOnly, Deprecated); // Deprecated options: // these weren't even ever used! conf.define_option("router", "max-routers", Deprecated); conf.define_option("router", "min-routers", Deprecated); // TODO: this may have been a synonym for [router]worker-threads conf.define_option("router", "threads", Deprecated); conf.define_option("router", "net-threads", Deprecated); is_relay = params.type == config::Type::Relay; } void ExitConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters&) { conf.define_option( "exit", "auth", FullClientOnly, MultiValue, Comment{ "Specify an optional authentication token required to use a non-public exit node.", "For example:", " auth=myfavouriteexit.loki:abc", "uses the authentication code `abc` whenever myfavouriteexit.loki is accessed.", "Can be specified multiple times to store codes for different exit nodes.", }, [this](std::string arg) { if (arg.empty()) throw std::invalid_argument{"Empty argument passed to '[exit]:auth'"}; const auto pos = arg.find(":"); if (pos == std::string::npos) { throw std::invalid_argument( "[exit]:auth invalid format, expects exit-address.loki:auth-token-goes-here"); } const auto addr = arg.substr(0, pos); auto auth = arg.substr(pos + 1); if (is_valid_sns(addr)) { sns_auth_tokens.emplace(std::move(addr), std::move(auth)); return; } try { NetworkAddress exit{addr}; if (!exit.client()) throw std::invalid_argument{"only .loki addresses can be used for exits"}; auth_tokens.emplace(std::move(exit), std::move(auth)); } catch (const std::exception& e) { throw std::invalid_argument("[exit]:auth invalid exit address: {}"_format(e.what())); } }); conf.define_option( "exit", "enable", FullClientOnly, Default{false}, assignment_acceptor(exit_enabled), Comment{ "Enable exit-node functionality for local lokinet instance.", }); conf.define_option( "exit", "policy", FullClientOnly, MultiValue, Comment{ "Specifies the IP traffic accepted by the local exit node traffic policy. If any are", "specified then only matched traffic will be allowed and all other traffic will be", "dropped. Examples:", " policy=tcp", "would allow all TCP/IP packets (regardless of port);", " policy=0x69", "would allow IP traffic with IP protocol 0x69;", " policy=udp/53", "would allow UDP port 53; and", " policy=tcp/smtp", "would allow TCP traffic on the standard smtp port (21).", }, [this](std::string arg) { // this will throw on error exit_policy.protocols.insert(net::ProtocolInfo::from_config(arg)); }); conf.define_option( "exit", "reserved-range", FullClientOnly, MultiValue, Comment{ "Reserve an ip range to use as an exit broker for a `.loki` address", "Specify a `.loki` address and a reserved ip range to use as an exit broker.", "Examples:", " reserved-range=whatever.loki", "would route all exit traffic through whatever.loki; and", " reserved-range=stuff.loki:100.0.0.0/24", "would route the IP range 100.0.0.0/24 through stuff.loki.", "This option can be specified multiple times (to map different IP ranges).", }, [this](std::string arg) { if (arg.empty()) return; std::variant range; const auto pos = arg.find(":"); if (pos == std::string::npos) range = ipv4{0} / 0; else { try { std::string input = arg.substr(pos + 1); if (input.find(":") != std::string::npos) // ipv6 range = parse_ipv6_range(input, 128); else range = parse_ipv4_range(input, 32); } catch (const std::exception& e) { throw std::invalid_argument{"[exit]:reserved-range invalid ip range: {}"_format(e.what())}; } arg.resize(pos); } if (is_valid_sns(arg)) sns_ranges[arg].push_back(std::move(range)); else { try { ranges[NetworkAddress{arg}].push_back(std::move(range)); } catch (const std::exception& e) { throw std::invalid_argument{"[exit]:reserved-range invalid address: {}"_format(arg)}; } } }); conf.define_option( "exit", "routed-range", FullClientOnly, MultiValue, Comment{ "Advertise that exit node routes exit traffic to the specified IP range. If omitted, the", "default is ALL public ranges. Can be set to public to indicate that this exit", "routes traffic to the public internet.", "For example:", " routed-range=10.0.0.0/16", " routed-range=public", "to advertise that this exit routes traffic to both the public internet, and to", "10.0.x.y addresses.", "", "Note that this option does not automatically configure network routing; that", "must be configured separately on the exit system to handle lokinet traffic.", }, [this](std::string arg) { if (arg == "public") exit_policy.ranges.push_back(ipv4{0} / 0); else { try { if (arg.find(':') != std::string::npos) exit_policy.ranges_v6.push_back(parse_ipv6_range(arg, 128)); else exit_policy.ranges.push_back(parse_ipv4_range(arg, 32)); } catch (const std::exception& e) { throw std::invalid_argument{"[exit]:routed-range invalid range '{}': {}"_format(arg, e.what())}; } } }); } void NetworkConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) { conf.add_section_comments( "network", { "Network settings related to network devices and communications.", }); conf.define_option( "network", "save-profiles", Default{params.type != config::Type::EmbeddedClient}, Hidden, assignment_acceptor(save_profiles)); conf.define_option("network", "profiling", Default{true}, Hidden, assignment_acceptor(enable_profiling)); conf.define_option("network", "profiles", Deprecated); conf.define_option( "network", "keyfile", ClientOnly, [this](std::string arg) { if (arg.empty()) return; keyfile = arg; if (check_path_op(keyfile)) log::info(logcat, "Client configured to try private key file at path: {}", keyfile->c_str()); else log::warning(logcat, "Bad input for client private key file ({}); using ephemeral...", arg); }, Comment{ "The private key to persist address with. If not specified the address will be", "ephemerally generated.", }); conf.define_option( "network", "auth-type", FullClientOnly, Comment{ "Set the endpoint authentication type.", "none/whitelist/lmq/file", }, [this](std::string arg) { if (arg == "file") auth_type = auth::AuthType::FILE; else if (arg == "lmq" || arg == "omq" || arg == "zmq") auth_type = auth::AuthType::OMQ; else if (arg == "whitelist") auth_type = auth::AuthType::WHITELIST; else if (arg == "" || arg == "none") auth_type = auth::AuthType::NONE; else throw std::invalid_argument{"invalid [network]:auth-type value: '{}'"_format(arg)}; }); conf.define_option( "network", "omq-auth-endpoint", FullClientOnly, assignment_acceptor(auth_endpoint), Comment{ "OMQ endpoint to talk to for authenticating new sessions", "ipc:///var/lib/lokinet/auth.socket", "tcp://127.0.0.1:5555", }); conf.define_option( "network", "omq-auth-method", FullClientOnly, Default{"llarp.auth"}, Comment{ "OMQ function to call for authenticating new sessions", "llarp.auth", }, [this](std::string arg) { if (arg.empty()) return; auth_method = std::move(arg); }); conf.define_option( "network", "auth-whitelist", FullClientOnly, MultiValue, Comment{ "manually add a remote endpoint by .loki address to the access whitelist", }, [this](std::string arg) { try { auth_whitelist.insert(NetworkAddress{arg}); } catch (const std::exception& e) { throw std::invalid_argument{ "[network]:auth-whitelist: invalid .loki address '{}': {}"_format(arg, e.what())}; } }); conf.define_option( "network", "auth-file", FullClientOnly, MultiValue, Comment{ "Read auth tokens from file to accept endpoint auth", "Can be provided multiple times", }, [this, rel_base = params.default_data_dir](std::filesystem::path arg) { if (!arg.empty() && arg.is_relative()) arg = rel_base / arg; if (not exists(arg)) throw std::invalid_argument{"cannot load auth file {}: file does not exist"_format(arg)}; auth_files.push_back(std::move(arg)); }); conf.define_option( "network", "auth-file-type", FullClientOnly, Comment{ "How to interpret the contents of an auth file.", #ifdef LOKINET_HAVE_CRYPT "Possible values: hash, plaintext", #else "Possible values: plaintext", #endif }, [this](std::string arg) { if (arg == "plain" || arg == "plaintext") auth_file_type = auth::AuthFileType::PLAIN; else if (arg == "hashed" || arg == "hashes" || arg == "hash") { #ifndef LOKINET_HAVE_CRYPT throw std::invalid_argument{"Hashed auth files are not supported by this Lokinet build"}; #endif auth_file_type = auth::AuthFileType::HASHES; } else throw std::invalid_argument{"Invalid auth file type '{}'"_format(arg)}; }); conf.define_option( "network", "auth-static", FullClientOnly, MultiValue, Comment{ "Manually add a static auth code to accept for endpoint auth", "Can be provided multiple times", }, [this](std::string arg) { auth_static_tokens.emplace(std::move(arg)); }); conf.define_option( "network", "reachable", FullClientOnly, Default{true}, assignment_acceptor(is_reachable), Comment{ "Determines whether we will pubish our service's ClientContact to the network (client default: TRUE)", }); conf.define_option("network", "hops", ClientOnly, Hidden, [](int) { log::warning( logcat, "[network]:hops is no longer supported; default path lengths applied. See the path options in the " "[paths] section instead"); }); conf.define_option("network", "paths", ClientOnly, Hidden, [](int) { log::error( logcat, "[network]:paths is no longer supported; default path numbers applied. See the path options in the " "[paths] section instead"); }); conf.define_option( "network", "auto-routing", FullClientOnly, Default{true}, Comment{ "Enable / disable automatic route configuration.", "When this is enabled and an exit is used Lokinet will automatically configure the", "operating system routes to route public internet traffic through the exit node.", "This is enabled by default, but can be disabled if advanced/manual exit routing", "configuration is desired."}, assignment_acceptor(enable_route_poker)); conf.define_option( "network", "blackhole-routes", FullClientOnly, Default{true}, Comment{ "Enable / disable route configuration blackholes.", "When enabled lokinet will drop IPv4 and IPv6 traffic (when in exit mode) that is " "not", "handled in the exit configuration. Enabled by default."}, assignment_acceptor(blackhole_routes)); conf.define_option( "network", "ifname", NotEmbedded, Comment{ "Interface name for lokinet traffic. If unset lokinet will look for a free name", "matching 'lokitunN', starting at N=0 (e.g. lokitun0, lokitun1, ...).", #ifdef __linux__ "", "On Linux, you can use '%d' in the name as a pattern to have the OS automatically choose", "a device name by replacing '%d' with a number to construct an unused interface name", #endif }, assignment_acceptor(_if_name)); conf.define_option( "network", "ifaddr", NotEmbedded, Comment{ "Local IP and netmask for lokinet traffic. For example, 172.16.0.1/16 to use", "172.16.0.1 for this lokinet instance and 172.16.x.y for remote peers. If omitted", "then lokinet will attempt to automatically select an unused private range.", "If you specify an all-0 address with range (e.g. 0.0.0.0/12) then lokinet will", "auto-select a private range of the given size.", }, [this](std::string arg) { try { _local_ip_net = parse_ipv4_net(arg); } catch (const std::exception& e) { throw std::invalid_argument{"[network]:ifaddr invalid value '{}': {}"_format(arg, e.what())}; } }); conf.define_option( "network", "ipv6-network", NotEmbedded, Hidden, Comment{ "Enables internal IPv6 traffic for lokinet. Can be set to:", " - false to disable IPv6 support. This is the default if omitted", " - true to enable IPv6 support and auto-detect a free private /64 network range", " - ::/80 to auto-detect a free private range of netmask 80 (change as needed) ", " instead of the default 64", " - An explicit private address and range to use, such as: fd00:abcd:1234::1/56", "", "Currently experimental and not supported.", }, [this](std::string arg) { if (arg.empty()) { enable_ipv6 = false; return; } if (auto b = parse_boolean(arg)) { enable_ipv6 = *b; return; } try { _local_ipv6_net = parse_ipv6_net(arg); enable_ipv6 = true; } catch (const std::exception& e) { throw std::invalid_argument{"[network]:ipv6-addr invalid value '{}': {}"_format(arg, e.what())}; } }); conf.define_option( "network", "mapaddr", FullClientOnly, MultiValue, Comment{ "Map a remote `.loki` address to always use a fixed local IP. For example:", " mapaddr=.loki:172.16.0.10", "maps `.loki` to `172.16.0.10` instead of using the next available IP.", "The given IP address must be inside the range configured by ifaddr=, and the", "remote `.loki` cannot be an ONS address"}, [this](std::string arg) { if (arg.empty()) return; const auto pos = arg.find(":"); if (pos == std::string::npos) throw std::invalid_argument{ "[endpoint]:mapaddr invalid entry '{}'; expected 'ADDR:IP'"_format(arg)}; auto addr_arg = std::string_view{arg}.substr(0, pos); auto ip_arg = arg.substr(pos + 1); try { NetworkAddress raddr{addr_arg}; // ipv6 if (ip_arg.find(':') != std::string_view::npos) _reserved_local_ipv6.emplace(raddr, ip_arg); else _reserved_local_ipv4.emplace(raddr, ip_arg); } catch (const std::exception& e) { throw std::invalid_argument{"[endpoint]:mapaddr invalid entry '{}': {}"_format(arg, e.what())}; } }); // TODO: support SRV records for routers, but for now client only conf.define_option( "network", "srv", FullClientOnly, MultiValue, Comment{ "Specify SRV Records for services hosted on the SNApp for protocols that use SRV", "records for service discovery. Each line specifies a single SRV record as:", " srv=_service._protocol priority weight port target.loki", "and can be specified multiple times as needed.", "For more info see", "https://docs.oxen.io/products-built-on-oxen/lokinet/snapps/hosting-snapps", "and general description of DNS SRV record configuration.", }, [this](std::string arg) { auto maybe_srv = dns::SRVData::from_srv_string(arg); if (not maybe_srv) throw std::invalid_argument{"Invalid SRV Record string: {}"_format(arg)}; srv_records.emplace(std::move(*maybe_srv)); }); conf.define_option("network", "path-alignment-timeout", Deprecated); conf.define_option( "network", "persist-addrmap-file", FullClientOnly, Comment{ "If given this specifies a file in which to record mapped local tunnel addresses so", "the same local address will be used for the same lokinet address on reboot. If this", "is not specified then the local IP of remote lokinet targets will not persist across", "restarts of lokinet.", }, [this, rel_base = params.default_data_dir](std::filesystem::path file) { if (!file.empty() && file.is_relative()) file = rel_base / file; static constexpr auto addrmap_errorstr = "Invalid entry in persist-addrmap-file"sv; if (file.empty()) throw std::invalid_argument("persist-addrmap-file cannot be empty"); if (not exists(file)) throw std::invalid_argument("persist-addrmap-file path invalid: {}"_format(file)); bool load_file = true; { constexpr auto ADDR_PERSIST_MODIFY_WINDOW = 1min; const auto last_write_time = std::filesystem::last_write_time(file); const auto now = decltype(last_write_time)::clock::now(); if (now < last_write_time or now - last_write_time > ADDR_PERSIST_MODIFY_WINDOW) { load_file = false; } } std::string data; if (auto maybe = util::OpenFileStream(file, std::ios_base::binary); maybe and load_file) { log::debug(logcat, "Config loading persisting address map file from path:{}", file); maybe->seekg(0, std::ios_base::end); const auto len = maybe->tellg(); maybe->seekg(0, std::ios_base::beg); data.resize(len); maybe->read(data.data(), len); } else { auto err = "Config could not load persisting address map file from path:{}"_format(file); log::warning(logcat, "{} {}", err, load_file ? "NOT FOUND" : "STALE"); } if (not data.empty()) { log::trace(logcat, "Config parsing address map data: {}", llarp::buffer_printer{data}); const auto parsed = oxenc::bt_deserialize(data); for (const auto& [key, value] : parsed) { try { quic::Address addr{key, 0}; std::variant ip; auto check_ip_okay = [](const std::optional& range, const auto& ip) { if (range) { bool bad = ip == range->ip || ip == range->to_range().ip; if constexpr (std::same_as) bad = bad || ip == range->broadcast(); if (bad) { log::warning( logcat, "{}: ignore invalid address map IP {}", addrmap_errorstr, ip); return false; } if (!range->contains(ip)) { log::warning( logcat, "{}: IP {} is outside the configured local range {}", addrmap_errorstr, ip, range->to_range()); return false; } } return true; }; if (addr.is_ipv4()) { if (!check_ip_okay(_local_ip_net, ip.emplace(addr.to_ipv4()))) continue; } else { if (!check_ip_okay(_local_ipv6_net, ip.emplace(addr.to_ipv6()))) continue; } const auto* arg = std::get_if(&value); if (not arg) { log::warning(logcat, "{}: {}", addrmap_errorstr, "not a string!"); continue; } if (is_valid_sns(*arg)) { log::warning(logcat, "{}: {}", addrmap_errorstr, "cannot accept ONS names!"); continue; } try { NetworkAddress netaddr{*arg}; if (auto* ip4 = std::get_if(&ip)) _reserved_local_ipv4.emplace(std::move(netaddr), std::move(*ip4)); else _reserved_local_ipv6.emplace(std::move(netaddr), std::move(std::get(ip))); } catch (const std::exception& e) { log::warning(logcat, "{}: invalid value {}: {}", addrmap_errorstr, *arg, e.what()); continue; } } catch (const std::exception& e) { log::warning( logcat, "Exception caught parsing key:value (key:{}) pair in addr persist file:{}", key, e.what()); } } } addr_map_persist_file = file; }); // Deprecated options: conf.define_option("network", "enabled", Deprecated); } void DnsConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) { conf.add_section_comments( "dns", { "DNS configuration", }); // Most non-linux platforms have loopback as 127.0.0.1/32, but linux uses 127.0.0.1/8 so // that we can bind to other 127.* IPs to avoid conflicting with something else that may be // listening on 127.0.0.1:53. constexpr std::array DefaultDNSBind{ #ifdef __linux__ #ifdef WITH_SYSTEMD // when we have systemd support add a random high port on loopback as well // see https://github.com/oxen-io/lokinet/issues/1887#issuecomment-1091897282 Default{"127.0.0.1:0"}, #endif Default{"127.3.2.1:53"}, #else Default{"127.0.0.1:53"}, #endif }; auto parse_addr_for_dns = [](const std::string& arg) { std::optional addr = std::nullopt; std::string_view arg_v{arg}, port; std::string host; uint16_t p{DEFAULT_DNS_PORT}; if (auto pos = arg_v.find(':'); pos != arg_v.npos) { host = arg_v.substr(0, pos); port = arg_v.substr(pos + 1); if (not llarp::parse_int(port, p)) log::info(logcat, "Failed to parse port in arg:{}, defaulting to DNS port 53", port); addr = quic::Address{host, p}; } return addr; }; conf.define_option( "dns", "upstream", FullClientOnly, MultiValue, Comment{ "Upstream resolver(s) to use as fallback for non-loki addresses.", "Multiple values accepted.", }, [this, parse_addr_for_dns](std::string arg) { if (not arg.empty()) { if (auto maybe_addr = parse_addr_for_dns(arg)) _upstream_dns.push_back(std::move(*maybe_addr)); else log::warning(logcat, "Failed to parse upstream DNS resolver address:{}", arg); } }); conf.define_option( "dns", "l3-intercept", FullClientOnly, Default{ platform::is_windows or platform::is_android or (platform::is_macos and not platform::is_apple_sysex)}, Comment{"Intercept all dns traffic (udp/53) going into our lokinet network interface " "instead of binding a local udp socket"}, assignment_acceptor(l3_intercept)); conf.define_option( "dns", "query-bind", FullClientOnly, #if defined(_WIN32) Default{"0.0.0.0:0"}, #else Hidden, #endif Comment{ "Address to bind to for sending upstream DNS requests.", }, [this, parse_addr_for_dns](std::string arg) { if (not arg.empty()) { if (auto maybe_addr = parse_addr_for_dns(arg)) _query_bind = std::move(*maybe_addr); else log::warning(logcat, "Failed to parse bind address for DNS queries:{}", arg); } }); conf.define_option( "dns", "bind", NotEmbedded, DefaultDNSBind, MultiValue, Comment{ "Address to bind to for handling DNS requests.", }, [this, parse_addr_for_dns](std::string arg) { if (not arg.empty()) { if (auto maybe_addr = parse_addr_for_dns(arg)) { _bind_addrs.push_back(std::move(*maybe_addr)); } else log::warning(logcat, "Failed to parse bind address for handling DNS requests:{}", arg); } }); conf.define_option( "dns", "add-hosts", FullClientOnly, Comment{"Add a hosts file to the dns resolver", "For use with client side dns filtering"}, [this, rel_base = params.default_data_dir](std::filesystem::path path) { if (path.empty()) return; if (path.is_relative()) path = rel_base / path; if (not exists(path)) throw std::invalid_argument{"cannot add hosts file {} as it does not exist"_format(path)}; hostfiles.emplace_back(std::move(path)); }); // Ignored option (used by the systemd service file to disable resolvconf configuration). conf.define_option( "dns", "no-resolvconf", FullClientOnly, Comment{ "Can be uncommented and set to 1 to disable resolvconf configuration of lokinet " "DNS.", "(This is not used directly by lokinet itself, but by the lokinet init scripts", "on systems which use resolveconf)", }); // forward the rest to libunbound conf.add_undeclared_handler( "dns", [this](auto, std::string_view key, std::string_view val) { extra_opts.emplace(key, val); }); } void LinksConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters&) { conf.add_section_comments( "bind", { "This section allows specifying the IPs that lokinet uses for incoming and outgoing", "connections. For simple setups it can usually be left blank, but may be required", "for routers with multiple IPs, or routers that must listen on a private IP with", "forwarded public traffic. It can also be useful for clients that want to use a", "consistent outgoing port for which firewall rules can be configured.", }); conf.define_option( "bind", "public-ip", Hidden, RelayOnly, public_ip_loader(public_addr, "[bind]:public-ip", "[router]:public-ip")); conf.define_option( "bind", "public-port", Hidden, RelayOnly, public_port_loader(public_addr, "[bind]:public-port", "[router]:public-port")); auto parse_addr_for_link = [](std::string_view arg) { quic::Address a = quic::Address::parse(arg, 0); if (a.is_loopback()) throw std::invalid_argument{"Invalid listen address: {} is a loopback address"_format(arg)}; if (a.is_ipv6() && a.is_any_addr()) a = quic::Address{ipv4{0, 0, 0, 0}, a.port()}; else if (a.is_ipv6() && a.is_ipv4_mapped_ipv6()) a.unmap_ipv4_from_ipv6(); else if (a.is_ipv6()) throw std::invalid_argument{"Invalid listen address: IPv6 addresses are not currently supported"}; return a; }; conf.define_option( "bind", "listen", Comment{ "IP and/or port for lokinet to bind to for inbound/outbound connections.", "", "If IP is omitted then lokinet will search for a local network interface with a", "public IP address and use that IP (and will exit with an error if no such IP is found", "on the system). If port is omitted then lokinet defaults to 1090 (routers) or 1091 (clients).", "", "Examples:", " listen=15.5.29.5:443", " listen=10.0.2.2", " listen=:1234", "", "Note that, when running as a relay, a private range IP address (like the second example", "above) requires also using [router]:public-ip/-port to specify the public IP address at", "which this router can be reached, and requires that traffic on that port is redirected to", "the listening internal address.", }, [this, parse_addr_for_link](const std::string& arg) { if (listen_addr) throw std::runtime_error{ "Multiple listen addresses found. If upgrading from an older lokinet, delete extra " "[bind]:inbound and [bind]:IP and use only one [bind]:listen"}; listen_addr = parse_addr_for_link(arg); }); conf.define_option( "bind", "inbound", RelayOnly, MultiValue, Hidden, [this, parse_addr_for_link](const std::string& arg) { if (listen_addr) throw std::runtime_error{ "Multiple listen addresses found. If upgrading from an older lokinet, delete extra " "[bind]:inbound and [bind]:IP and use only one [bind]:listen"}; listen_addr = parse_addr_for_link(arg); log::warning( logcat, "Loaded listen address {} from deprecated [bind]:inbound option; please update your config to " "use [bind]:listen instead", *listen_addr); }); conf.define_option("bind", "outbound", MultiValue, Deprecated, Hidden); conf.add_undeclared_handler("bind", [this](std::string_view, std::string_view key, std::string_view val) { // special case: old lokinet used '*' for outbound port, which now does nothing if (key == "*") { log::warning( logcat, "[bind]:*=PORT is deprecated and no longer does anything in this version of Lokinet"); return; } log::warning( logcat, "[bind]:{} is deprecated: Please update your config to use [bind]:listen instead", key); // Otherwise you could have either `A.B.C.D=PORT` or `IFNAME=port`. The latter was // almost never used, and so we only look for the format and error on the latter. if (listen_addr) throw std::runtime_error{ "Multiple listen addresses found. If upgrading from an older lokinet, replace extra " "[bind]:inbound=/IP= settings with a single [bind]:listen="}; uint16_t port{0}; quic::Address temp; try { if (!llarp::parse_int(val, port)) throw std::runtime_error{"Could not parse port"}; temp = quic::Address{std::string{key}, port}; } catch (const std::exception&) { throw std::runtime_error{ "Invalid [bind] deprecated config item: {}={}. " "Please replace with a [bind]:listen=... directive"_format(key, val)}; } listen_addr = std::move(temp); log::warning( logcat, "[bind]:{0}={1} is deprecated; please replace with [bind] config entry: listen={0}:{1}", key, val); }); } void ApiConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) { conf.add_section_comments( "api", { "JSON API settings", }); constexpr std::array DefaultRPCBind{ Default{"tcp://127.0.0.1:1190"}, #ifndef _WIN32 Default{"ipc://rpc.sock"}, #endif }; conf.define_option( "api", "enabled", NotEmbedded, Default{params.type == config::Type::FullClient}, assignment_acceptor(enable_rpc_server), Comment{ "Determines whether or not the OMQ JSON API is enabled. By default this is enabled for clients, " "disabled for relays", }); conf.define_option( "api", "bind", NotEmbedded, DefaultRPCBind, MultiValue, [this, first = true](std::string arg) mutable { if (first) { rpc_bind_addrs.clear(); first = false; } if (arg.find("://") == std::string::npos) { arg = "tcp://" + arg; } rpc_bind_addrs.push_back(std::move(arg)); }, Comment{ "IP addresses and ports to bind to.", "Recommend localhost-only for security purposes.", }); conf.define_option("api", "authkey", Deprecated); // TODO: this was from pre-refactor: // TODO: add pubkey to whitelist } void LokidConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters&) { conf.add_section_comments( "lokid", { "Settings for communicating with oxend", }); conf.define_option( "lokid", "disable-testing", Default{false}, Hidden, RelayOnly, Comment{"Development option: set to true to disable reachability testing when using", "testnet"}, assignment_acceptor(disable_testing)); conf.define_option( "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", "ipc://OXEND-DATA-DIRECTORY/oxend.sock, such as:", " rpc=ipc:///var/lib/oxen/oxend.sock", " rpc=ipc:///home/USER/.oxen/oxend.sock", "but can use (non-default) TCP if oxend is configured that way:", " rpc=tcp://127.0.0.1:5678", }, [this](std::string arg) { #ifndef LOKINET_EMBEDDED_ONLY oxenmq::address test_valid{arg}; #endif rpc_addr = std::move(arg); }); // Deprecated options: conf.define_option("lokid", "jsonrpc", RelayOnly, Hidden, [](std::string arg) { if (arg.empty()) return; throw std::invalid_argument( "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc config " "option instead with oxend's lmq-local-control address -- typically a value such as " "rpc=ipc:///var/lib/oxen/oxend.sock or rpc=ipc:///home/snode/.oxen/oxend.sock"); }); conf.define_option("lokid", "enabled", RelayOnly, Deprecated); conf.define_option("lokid", "username", Deprecated); conf.define_option("lokid", "password", Deprecated); conf.define_option("lokid", "service-node-seed", Deprecated); } void BootstrapConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters&) { conf.add_section_comments( "bootstrap", { "Configure nodes that will bootstrap us onto the network", }); conf.define_option( "bootstrap", "add-node", MultiValue, Comment{ "Specify a bootstrap file containing a list of signed RelayContacts of service nodes", "which can act as a bootstrap. Can be specified multiple times. If set this overrides", "the built-in seed node list.", }, [this](std::string arg) { if (arg.empty()) throw std::invalid_argument("cannot use empty filename as bootstrap"); files.emplace_back(std::move(arg)); if (not exists(files.back())) throw std::invalid_argument("file does not exist: " + arg); }); } void LoggingConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) { conf.add_section_comments( "logging", { "Logging settings", }); conf.define_option( "logging", "type", Default{ params.type == config::Type::EmbeddedClient ? "none" : platform::is_android or platform::is_apple ? "system" : "print"}, [this](std::string arg) { if (arg == "none") type = std::nullopt; else type = log::type_from_string(arg); }, Comment{ "Log type (format). Valid options are:", " print - print logs to standard output", " system - logs directed to the system logger (syslog/eventlog/etc.)", " file - plaintext formatting to a file", (params.type == config::Type::EmbeddedClient ? " none - do not reset the logging system (for embedded " "use with external oxen::logging)" : ""), }); conf.define_option( "logging", "level", Default{ params.type == config::Type::Relay ? "warn" : params.type == config::Type::FullClient ? "info" : ""}, [this](std::string arg) { levels = std::move(arg); }, Comment{ "Minimum log severity level to print. Logging below this level will be ignored.", "Can also be set to a comma-separated list of individual categories, such as:", " *=warn, logcat123=debug", "", "Valid log levels, in ascending order, are:", " trace, debug, info, warn, error, critical, off", }); conf.define_option( "logging", "file", Default{""}, assignment_acceptor(file), Comment{ "When using type=file this is the output filename.", }); } void PathConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters&) { conf.add_section_comments( "paths", { "Settings related to path selection such as number of hops and selection criteria", }); conf.define_option( "paths", "edge-connections", Default{CLIENT_ROUTER_CONNECTIONS}, ClientOnly, Comment{ "Minimum number of routers lokinet client will attempt to maintain direct (i.e. \"edge\")", "connections to. All paths will start through one of these edges.", "", "Lokinet may use more than this number of edges in single-hop connection mode", "(see [paths]:client-hops) and may use fewer connections if limited by [paths]:strict-edge."}, lower_bounded_assignment_acceptor(edge_connections, 1, "[paths]:edge-connections")); conf.define_option( "paths", "outbound-paths", ClientOnly, Default{2}, Comment{ "Number of paths to maintain per active outbound connection to a remote client or relay.", "Only one path is actively used at a time, but others are used for regular path rotation", "and as a fallback for path failure.", "", "Note that this value applies to EACH outbound connection separately: if you have active", "connections to 5 clients and 3 snodes, lokinet will maintain 16 outbound paths (at the", "default setting of 2).", "", "Setting this value to 1 is allowed, but will result in brief periods of packet loss", "whenever paths expire due to the lack of allowed backup path.", }, bounded_assignment_acceptor(outbound_paths, 1, 4, "[paths]:outbound-paths")); conf.define_option( "paths", "client-hops", ClientOnly, Default{3}, Comment{ "Number of hops to use when establishing a connection to a service node relay to", "communicate through that relay to another client.", "", "The overall number of hops to the remote client is this value PLUS the number of inbound", "hops the other client has configured for their inbound hops (via [paths]:inbound-hops).", "", "Setting this value to 1 puts lokinet into single-hop mode for the connection from this", "client to the aligned pivot router, which potentially weakens connection privacy as", "your public IP will be observable to any service node listed as a pivot for any remote", "client that you connect to."}, bounded_assignment_acceptor(client_hops, 1, path::BUILD_LENGTH, "[paths]:client-hops")); conf.define_option( "paths", "relay-hops", ClientOnly, Comment{ "Number of hops to use when establishing a connection to talk to a service node.", "", "A value of 1 results in establishing direct connection to the snode (i.e. only", "encryption but no onion routing); 2 would select one intermediate snode to onion", "route through; 4 uses three intermediates, and so on, up to the maximum of 8.", "", "Additional hops increases privacy but also increase latency and reduces network", "performance through the path.", "", "If not set, this default to one greater than the value of [paths]:client-hops.", "", "Setting this value to 1 puts lokinet into single-hop mode for the connection from this", "client to service node (i.e. `.snode` addresses) which potentially weakens connection", "privacy as any service nodes you connect to will be able to observe your public IP."}, bounded_assignment_acceptor(relay_hops_, 1, path::BUILD_LENGTH, "[paths]:relay-hops")); conf.define_option( "paths", "inbound-paths", ClientOnly, Default{4}, Comment{ "Number of local paths that Lokinet maintains for both network reachability (i.e. remote", "clients connecting to this instance) and network communication such as looking up", "client lto maintain for network reachability and for general network requests", "", "This value does NOT apply to paths that are built to reach external clients or relays.", }, bounded_assignment_acceptor(inbound_paths, 1, 10, "[paths]:local-paths")); conf.define_option( "paths", "inbound-paths-extra", FullClientOnly, Hidden, Default{0}, Comment{ "Extra inbound paths to use for Lokinet connectivity. This option is hidden as it is not", "meant for normal Lokinet use, and may be removed or replaced without warning in the future", }, lower_bounded_assignment_acceptor(inbound_paths_extra, 0, "[paths]:inbound-paths-extra")); conf.define_option( "paths", "inbound-hops", ClientOnly, Comment{ "Number of hops to use for inbound and general request connections (see [paths]:inbound-paths).", "", "When a remote Lokinet is connecting to this instance, this controls the path length of the", "local side of the full client-to-client path (i.e. from the common relay \"pivot\" to this", "Lokinet instance).", "", "If not set, this value defaults to the same value as [paths]:client-hops.", }, bounded_assignment_acceptor(inbound_hops_, 1, path::BUILD_LENGTH, "[paths]:inbound-hops")); conf.define_option( "paths", "inbound-pivot-reuse", ClientOnly, Default{1}, Comment{ "This configures the maximum number of times a single relay may be used as a path pivot.", "The default is one, meaning each inbound path is built to a distinct pivot, but special", "cases (such as a one-hop reachable endpoint using a small number of pivots) may want to", "increase this to allow multiple pivots to be used at once. Leaving this at the default", "of 1 is recommended for most cases.", }, lower_bounded_assignment_acceptor(inbound_pivot_reuse, 1, "[paths]:inbound-pivot-reuse")); conf.define_option( "paths", "unique-range-size", Default{24}, ClientOnly, [this](int arg) { if (arg > 32 or (arg < 4 and arg != 0)) throw std::invalid_argument{"[paths]:unique-range-size must be between 4 and 32, or 0"}; unique_hop_netmask = static_cast(arg); }, Comment{ "Netmask for router path selection; each router must be from a distinct IPv4 subnet", "of the given size. Defaults to 24.", "", "For instance, setting this to 16 selects routers for each path that have distinct", "x.y.*.* IP addresses; 32 merely requires that each router have a unique IP. Setting", "this to 0 disables IP uniqueness entirely (i.e. paths can be selected that go through", "different Lokinet routers on the same IP)", }); conf.define_option( "paths", "acceptable-expiry", Default{300s}, ClientOnly, Comment{ "The minimum expiry time a path/pivot must have for it to be eligible when switching", "to a new path. Inactive paths older than this will be replaced with new paths.", }, bounded_assignment_acceptor(acceptable_expiry, 0s, path::MAX_LIFETIME / 2, "acceptable-expiry")); conf.define_option( "paths", "min-expiry", Default{60s}, ClientOnly, Comment{ "The minimum allowed path/pivot expiry time (in seconds) of a currently active outbound path.", "When an active path reaches an expiry less than this value then the path to the remote will", "be rotated immediately to use a newer path.", "", "This value cannot be larger than acceptable-expiry.", }, bounded_assignment_acceptor(min_expiry, 0s, path::MAX_LIFETIME / 2, "min-expiry")); conf.define_option( "paths", "build-timeout", ClientOnly, Default{10s}, Comment{ "How long to wait for a session or path to establish before timing out the attempt.", "Value is in seconds, or milliseconds with an ms suffix (e.g. 2500ms).", }, bounded_assignment_acceptor(build_timeout, 1ms, 1min, "[paths]:build-timeout")); conf.add_options_validator([this] { if (min_expiry > acceptable_expiry) throw std::invalid_argument{"[paths]:min-expiry cannot be longer than [paths]:acceptable-expiry"}; }); conf.define_option( "paths", "ping-interval", Default{5s}, ClientOnly, Comment{"How frequently to send pings along built paths to test that they are still alive."}, lower_bounded_assignment_acceptor(ping_interval, 1s, "[paths]:ping-interval")); conf.define_option( "paths", "max-missed-pings", Default{5}, ClientOnly, Comment{ "The maximum number of consecutive missed pings (see ping-interval) allowed for a path. If a path", "misses more than this, the path will be considered to have died and be replaced."}, lower_bounded_assignment_acceptor(max_missed_pings, 0, "[paths]:max-missed-pings")); #ifdef LOKINET_DEBUG_PATH_SEED conf.define_option( "paths", "debug-path-seed", ClientOnly, Hidden, assignment_acceptor(debug_path_seed)); #endif conf.define_option( "paths", "strict-edge", ClientOnly, MultiValue, [this](std::string value) { RouterID router; if (value.size() == 64 && oxenc::is_hex(value)) oxenc::from_hex(value.begin(), value.end(), router.begin()); else if (not router.from_relay_address(value)) throw std::invalid_argument{"[paths]:strict-edge: Invalid .snode pubkey: {}"_format(value)}; if (not strict_edges.insert(router).second) throw std::invalid_argument{ "[paths]:strict-edge: Duplicate strict connect .snode value: {}"_format(value)}; }, Comment{ R"(List of service node public keys of "edge" nodes (also known as "first hops") that)", "Lokinet will exclusively use when establishing paths through the network. You can use", "this to always use closer (i.e. lower latency) first hops, or to limit which network", "nodes see connections from your IP address.", "", "Public keys can be provided either in native lokinet address format (ADDR.snode), or using", "the 64-character hexademical pubkey notation common used for Session service nodes.", "Specify this option multiple times to specify multiple allowed edge nodes.", "", "Note that only registered service node pubkeys will be used, and so connectivity will be", "lost entirely if all of the listed pubkeys are or become deregistered.", "", "This option is incompatible with single-hop outbound path mode (see `[paths]:client-hops`", "and `[paths]:relay-hops`).", "", "Note that if bootstrapping is needed a connection will be made to the configured bootstrap", "nodes to obtain an initial router list. See [bootstrap]:add-node if you want to also", "override the nodes used for bootstrapping."}); conf.add_options_validator([this] { if (strict_edges.empty()) return; if (client_hops == 1) throw std::invalid_argument{ "[paths]:strict-edge cannot be used with [paths]:client-hops=1 single hop mode"}; if (relay_hops_ and *relay_hops_ == 1) throw std::invalid_argument{ "[paths]:strict-edge cannot be used with [paths]:relay-hops=1 single hop mode"}; }); conf.define_option( "paths", "blacklist-snode", ClientOnly, MultiValue, Comment{ "Adds a lokinet relay `.snode` address to the list of relays to avoid when", "connecting to edges or building paths. Can be specified multiple times.", }, [this](std::string arg) { RouterID id; if (not id.from_relay_address(arg)) throw std::invalid_argument{"Invalid RouterID: {}"_format(arg)}; auto itr = snode_blacklist.emplace(std::move(id)); if (not itr.second) throw std::invalid_argument{"Duplicate blacklist-snode: {}"_format(arg)}; }); #ifdef WITH_GEOIP conf.defineOption( "paths", "exclude-country", ClientOnly, MultiValue, [this](std::string arg) { m_ExcludeCountries.emplace(lowercase_ascii_string(std::move(arg))); }, Comment{ "Exclude a country given its 2 letter country code from being used in path builds.", "For example:", " exclude-country=DE", "would avoid building paths through routers with IPs in Germany.", "This option can be specified multiple times to exclude multiple countries", "Note that this option does not affect the final relay or pivot in outgoing paths", }); #endif } std::unique_ptr Config::make_gen_params() const { auto cgp = std::make_unique(); cgp->default_data_dir = data_dir; cgp->type = type; return cgp; } Config::Config(config::Type type, std::filesystem::path conf_file) : data_dir{conf_file.parent_path()}, type{type} { auto ini = util::file_to_string(conf_file); load_config_data(std::move(ini), std::move(conf_file)); } Config::Config(config::Type type, std::string ini, std::filesystem::path default_data_dir) : data_dir{std::move(default_data_dir)}, type{type} { load_config_data(std::move(ini)); } static std::filesystem::path overrides_dir(const std::filesystem::path& datadir) { return datadir / "conf.d"; } void Config::save() { const auto overridesDir = overrides_dir(data_dir); if (not exists(overridesDir)) create_directories(overridesDir); parser.save(); } void Config::override(std::string section, std::string key, std::string value) { parser.add_override(overrides_dir(data_dir) / "overrides.ini", section, key, value); } void Config::load_overrides(ConfigDefinition& conf) const { ConfigParser parser; const auto overridesDir = overrides_dir(data_dir); if (exists(overridesDir)) { for (const auto& f : std::filesystem::directory_iterator{overridesDir}) { if (not f.is_regular_file() or f.path().extension() != ".ini") continue; ConfigParser parser; try { parser.load_file(f.path()); } catch (const std::exception& e) { throw std::runtime_error{"Failed to load config file {}: {}"_format(f.path().string(), e.what())}; } parser.iter_all_sections([&](std::string_view section, const SectionValues& values) { for (const auto& [k, v] : values) conf.add_config_value(section, k, v); }); } } } void Config::add_default(std::string section, std::string key, std::string val) { additional.emplace_back(std::array{section, key, val}); } void Config::load_config_data(std::string ini, std::optional filename) { #ifdef LOKINET_EMBEDDED_ONLY if (type != Type::EmbeddedClient) throw std::runtime_error{ "This lokinet build only supports embedded clients, not {}"_format(to_string(type))}; #endif auto params = make_gen_params(); ConfigDefinition conf{type}; add_backcompat_opts(conf); init_config(conf, *params); for (const auto& item : additional) { conf.add_config_value(item[0], item[1], item[2]); } parser.clear(); if (filename) parser.set_filename(*filename); else parser.set_filename(std::filesystem::path{}); parser.load_from_str(std::move(ini)); parser.iter_all_sections([&](std::string_view section, const SectionValues& values) { for (const auto& pair : values) { conf.add_config_value(section, pair.first, pair.second); } }); load_overrides(conf); conf.process(); } void Config::init_config(ConfigDefinition& conf, const ConfigGenParameters& params) { router.define_config_options(conf, params); exit.define_config_options(conf, params); network.define_config_options(conf, params); paths.define_config_options(conf, params); dns.define_config_options(conf, params); links.define_config_options(conf, params); api.define_config_options(conf, params); lokid.define_config_options(conf, params); bootstrap.define_config_options(conf, params); logging.define_config_options(conf, params); } void Config::add_backcompat_opts(ConfigDefinition& conf) { // These config sections don't exist anymore: conf.define_option("system", "user", Deprecated); conf.define_option("system", "group", Deprecated); conf.define_option("system", "pidfile", Deprecated); conf.define_option("netdb", "dir", Deprecated); conf.define_option("metrics", "json-metrics-path", Deprecated); } void ensure_config(std::filesystem::path dataDir, std::filesystem::path confFile, bool overwrite, config::Type type) { // fail to overwrite if not instructed to do so if (exists(confFile) && !overwrite) { log::info(logcat, "Config file already exists; NOT creating new config"); return; } const auto parent = confFile.parent_path(); // create parent dir if it doesn't exist if ((not parent.empty()) and (not exists(parent))) { create_directory(parent); } log::info(logcat, "Attempting to create config file for {} at file path:{}", to_string(type), confFile); llarp::Config config{type, "", dataDir}; auto confStr = config.generate_config_base(); try { util::buffer_to_file(confFile, confStr); } catch (const std::exception& e) { throw std::runtime_error{"Failed to write config data to {}: {}"_format(confFile, e.what())}; } log::info(logcat, "Generated new config (path: {})", confFile); } std::string Config::generate_config_base() { auto params = make_gen_params(); llarp::ConfigDefinition def{type}; init_config(def, *params); return def.generate_ini_config(true); } } // namespace llarp ================================================ FILE: llarp/config/config.hpp ================================================ #pragma once #include "definition.hpp" #include "ini.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace llarp { using SectionValues = llarp::ConfigParser::SectionValues; using ConfigMap = llarp::ConfigParser::ConfigMap; inline constexpr uint16_t DEFAULT_CLIENT_PORT{1091}; inline constexpr uint16_t DEFAULT_RELAY_PORT{1090}; inline const quic::Address DEFAULT_CLIENT_ADDR{"0.0.0.0", DEFAULT_CLIENT_PORT}; inline constexpr uint16_t DEFAULT_DNS_PORT{53}; inline constexpr int CLIENT_ROUTER_CONNECTIONS{4}; // TODO: don't use these maps. they're sloppy and difficult to follow /// Small struct to gather all parameters needed for config generation to reduce the number of /// parameters that need to be passed around. struct ConfigGenParameters { ConfigGenParameters() = default; virtual ~ConfigGenParameters() = default; ConfigGenParameters(const ConfigGenParameters&) = delete; ConfigGenParameters(ConfigGenParameters&&) = delete; config::Type type; std::filesystem::path default_data_dir; /// get network platform (virtual for unit test mocks) virtual const llarp::net::Platform* net_ptr(); }; struct RouterConfig { NetID net_id = NetID::MAINNET; std::filesystem::path data_dir; bool block_bogons = false; int worker_threads = -1; int net_threads = -1; size_t job_que_size = 0; std::optional rc_file; bool is_relay = false; std::optional public_addr; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; /// config for path hop selection struct PathConfig { int edge_connections{CLIENT_ROUTER_CONNECTIONS}; // If non-empty then *only* use these nodes for first hops. (Except for single-hop outbound // paths, which ignore this). std::unordered_set strict_edges; // Blacklist of relays to avoid using for edges or path hops std::unordered_set snode_blacklist; /// Number of paths to maintain for inbound reachability and network queries (such as /// looking up client contacts). int inbound_paths = 4; int inbound_paths_extra = 0; /// Number of times the same relay can be used as an inbound path pivot. The default is 1, /// which means every inbound path uses a distinct relay. int inbound_pivot_reuse = 1; /// Length of the "inbound" paths we use for inbound connections and network queries. /// If unset, use client_hops. std::optional inbound_hops_; // Retrieves the above, with built-in fallback to the client_hops value if not set. int inbound_hops() const { return inbound_hops_.value_or(client_hops); } /// Number of paths to maintain to *each* outgoing remote (relay or snode). int outbound_paths = 2; /// Number of hops when establishing a session to a relay (i.e. to a .snode, not *through* a /// relay to reach a client). std::optional relay_hops_; /// Retrieves the working value for relay-hops: the value if explicitly set, else one more /// than the configured client hops. int relay_hops() const { return relay_hops_.value_or(std::min(client_hops + 1, path::BUILD_LENGTH)); } /// Number of hops when building an aligned path to a relay to reach a client on the other /// side. int client_hops = 3; /// in our hops what netmask will we use for unique ips for hops /// i.e. 32 for every hop unique ip, 24 unique /24 per hop, etc uint8_t unique_hop_netmask{0}; // TODO: some day, if we ever support routers using IPv6, there would need to be a different // ipv6 netmask value. std::chrono::seconds min_expiry = 1min; std::chrono::seconds acceptable_expiry = 5min; std::chrono::milliseconds build_timeout{10s}; std::chrono::seconds ping_interval{5s}; int max_missed_pings{5}; // DEBUG ONLY: if set, this uses a repeatable RNG with the given seed for reproducible path // selection. This option only has an effect if lokinet is configured with // -DLOKINET_DEBUG_PATH_SEED=ON (which is disabled by default). std::optional debug_path_seed; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; /** TODO: - finalize supervenience of ExitConfig over deprecated config entries */ /// Config options related to exit node services struct ExitConfig { bool exit_enabled{false}; // Used by RemoteHandler to provide auth tokens for remote exits std::unordered_map auth_tokens; std::unordered_map sns_auth_tokens; net::ExitPolicy exit_policy; // Remote client ONS exit addresses mapped to local IP ranges pending ONS address resolution // Reserved local IP ranges mapped to remote client ONS addresses (pending ONS resolution) std::unordered_map>> sns_ranges; // Reserved local IP ranges mapped to remote client exit addresses std::unordered_map>> ranges; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct NetworkConfig { bool enable_profiling{false}; bool save_profiles{false}; std::optional keyfile; bool enable_ipv6{false}; bool is_reachable{false}; /* Auth specific config */ auth::AuthType auth_type = auth::AuthType::NONE; auth::AuthFileType auth_file_type = auth::AuthFileType::HASHES; std::optional auth_endpoint; std::optional auth_method; std::unordered_set auth_whitelist; std::unordered_set auth_static_tokens; std::vector auth_files; std::unordered_set srv_records; /* TESTNET: Under modification */ // Contents of this file are read directly into ::_reserved_local_addrs std::optional addr_map_persist_file; // the only member that refers to an actual interface std::optional _if_name; std::optional _local_ip_net; // [network]:ifaddr std::optional _local_ipv6_net; // [network]:ipv6 // Remote exit or hidden service addresses mapped to fixed local IP addresses // TODO: // - load directly into TunEndpoint mapping // - when a session is created, check mapping when assigning IP's std::unordered_map _reserved_local_ipv4; std::unordered_map _reserved_local_ipv6; // TESTNET: moved into ExitConfig! bool allow_exit{false}; // Used by RemoteHandler to provide auth tokens for remote exits std::unordered_map exit_auths; std::unordered_map sns_exit_auths; std::optional traffic_policy; // TESTNET: move into ExitConfig! bool enable_route_poker{false}; bool blackhole_routes{false}; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct DnsConfig { bool l3_intercept{false}; std::vector hostfiles; /* TESTNET: Under modification */ std::vector _upstream_dns; quic::Address _default_dns{"9.9.9.10", DEFAULT_DNS_PORT}; std::optional _query_bind; std::vector _bind_addrs; // Deprecated // std::vector upstream_dns; // std::optional query_bind; // std::vector bind_addr; /*************************************/ std::unordered_multimap extra_opts; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct LinksConfig { // DEPRECATED -- use [router]:public_addr/port instead std::optional public_addr; std::optional listen_addr; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct ApiConfig { bool enable_rpc_server = false; std::vector rpc_bind_addrs; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct LokidConfig { std::filesystem::path id_keyfile; std::string rpc_addr; bool disable_testing = false; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct BootstrapConfig { std::vector files; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct LoggingConfig { // Log type. If nullopt then lokinet will not set up logging sinks at all (this is // primarily aimed at embedded clients that have already set up logging). std::optional type = log::Type::Print; // levels can either be just a level ("warn"), or a list of cat levels such as: // "*=warning, cat1=debug, cat2*=trace". See oxen-logging for more details. If empty then // logging levels will not be set at all (and, again, is most useful for embedded clients). std::string levels; std::string file; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); }; struct Config { // Creates a config for the given lokinet instance type (relay, full client, or embedded // client), loading configuration data from the given string, if given (all default config // otherwise). The default data directory (if not explicit set in the given config string) // can optionally be provided. If omitted (and not set in the string) it defaults to cwd. Config( config::Type type, std::string config = "", std::filesystem::path default_data_dir = std::filesystem::current_path()); // Creates a config for the given lokinet instance type (relay, full client, or embedded // client), loading configuration data from an existing file. The default data directory // (if not set in the config itself) will be the directory containing the given config file. Config(config::Type type, std::filesystem::path config_file); Config(Config&&) = default; Config(const Config&) = default; Config& operator=(Config&&) = default; Config& operator=(const Config&) = default; virtual ~Config() = default; /// create generation params (virtual for unit test mock) virtual std::unique_ptr make_gen_params() const; RouterConfig router; ExitConfig exit; NetworkConfig network; PathConfig paths; DnsConfig dns; LinksConfig links; ApiConfig api; LokidConfig lokid; BootstrapConfig bootstrap; LoggingConfig logging; // Initialize config definition void init_config(ConfigDefinition& conf, const ConfigGenParameters& params); /// Insert config entries for backwards-compatibility (e.g. so that the config system will /// tolerate old values that are no longer accepted) /// /// @param conf is the config to modify void add_backcompat_opts(ConfigDefinition& conf); std::string generate_config_base(); void save(); void override(std::string section, std::string key, std::string value); void add_default(std::string section, std::string key, std::string value); bool relay() const { return type == config::Type::Relay; } bool embedded() const { return type == config::Type::EmbeddedClient; } bool client() const { return !relay(); } private: void load_config_data(std::string ini, std::optional fname = std::nullopt); void load_overrides(ConfigDefinition& conf) const; std::vector> additional; ConfigParser parser; std::filesystem::path data_dir{std::filesystem::current_path()}; config::Type type; }; // Ensures that a conf file exists, writing a default one if not present. Only for full // clients/routers (i.e. not embedded clients). void ensure_config( std::filesystem::path dataDir, std::filesystem::path confFile, bool overwrite, config::Type type); } // namespace llarp ================================================ FILE: llarp/config/definition.cpp ================================================ #include "definition.hpp" #include #include #include #include #include namespace llarp { static auto logcat = log::Cat("config.def"); static constexpr std::array true_values = {"true", "TRUE", "T", "on", "ON", "1", "yes", "enable", "enabled"}; static constexpr std::array false_values = {"false", "FALSE", "F", "off", "OFF", "1", "no", "disable", "disabled"}; std::optional parse_boolean(std::string_view input) { if (std::ranges::any_of(true_values, [&input](const auto& v) { return input == v; })) return true; if (std::ranges::any_of(false_values, [&input](const auto& v) { return input == v; })) return false; return std::nullopt; } template <> bool OptionDefinition::from_string(const std::string& input) { if (auto b = parse_boolean(input)) return *b; throw std::invalid_argument{"{} is not a valid bool"_format(input)}; } ConfigDefinition& ConfigDefinition::define_option(std::unique_ptr def) { using namespace config; // If explicitly deprecated or is a {client,relay} option in a {relay,client} config then // add a dummy, warning option instead of this one. bool bad = def->deprecated || (type == config::Type::Relay && def->client_only) || (type != config::Type::Relay && def->relay_only) || (type == config::Type::EmbeddedClient && def->no_embedded); if (bad) return define_option( def->section, def->name, MultiValue, Hidden, [deprecated = def->deprecated, type = type, opt = "[{}]:{}"_format(def->section, def->name)]( std::string_view) { log::warning( logcat, "*** WARNING: The config option {} is {} and has been ignored", opt, (deprecated ? "deprecated" : "invalid in {} configuration files"_format(to_string(type)))); }); auto [sectionItr, newSect] = definitions.try_emplace(def->section); if (newSect) section_ordering.push_back(def->section); auto& section = sectionItr->first; auto [it, added] = definitions[section].try_emplace(std::string{def->name}, std::move(def)); if (!added) throw std::invalid_argument{"definition for [{}]:{} already exists"_format(def->section, def->name)}; definition_ordering[section].push_back(it->first); if (!it->second->comments.empty()) add_option_comments(section, it->first, std::move(it->second->comments)); return *this; } ConfigDefinition& ConfigDefinition::add_config_value( std::string_view section, std::string_view name, std::string_view value) { // see if we have an undeclared handler to fall back to in case section or section:name is // absent auto undItr = undeclared_handlers.find(std::string(section)); bool haveUndeclaredHandler = (undItr != undeclared_handlers.end()); // get section, falling back to undeclared handler if needed auto secItr = definitions.find(std::string(section)); if (secItr == definitions.end()) { // fallback to undeclared handler if available if (not haveUndeclaredHandler) throw std::invalid_argument{"unrecognized section [{}]"_format(section)}; auto& handler = undItr->second; handler(section, name, value); return *this; } // section was valid, get definition by name // fall back to undeclared handler if needed auto& sectionDefinitions = secItr->second; auto defItr = sectionDefinitions.find(std::string(name)); if (defItr != sectionDefinitions.end()) { std::unique_ptr& definition = defItr->second; definition->parse_value(std::string(value)); return *this; } if (not haveUndeclaredHandler) throw std::invalid_argument{"unrecognized option [{}]: {}"_format(section, name)}; auto& handler = undItr->second; handler(section, name, value); return *this; } void ConfigDefinition::add_undeclared_handler(const std::string& section, UndeclaredValueHandler handler) { auto itr = undeclared_handlers.find(section); if (itr != undeclared_handlers.end()) throw std::logic_error{"section {} already has a handler"_format(section)}; undeclared_handlers[section] = std::move(handler); } void ConfigDefinition::remove_undeclared_handler(const std::string& section) { auto itr = undeclared_handlers.find(section); if (itr != undeclared_handlers.end()) undeclared_handlers.erase(itr); } void ConfigDefinition::add_options_validator(std::function validator) { options_validators.push_back(std::move(validator)); } void ConfigDefinition::validate_required_fields() { visit_sections([&](const std::string& section, const DefinitionMap&) { visit_definitions(section, [&](const std::string&, const std::unique_ptr& def) { if (def->required and def->get_number_found() < 1) { throw std::invalid_argument{"[{}]:{} is required but missing"_format(section, def->name)}; } // should be handled earlier in OptionDefinition::parse_value() assert(def->get_number_found() <= 1 or def->multi_valued); }); }); } void ConfigDefinition::accept_all_options() { visit_sections([this](const std::string& section, const DefinitionMap&) { visit_definitions(section, [](const std::string&, const std::unique_ptr& def) { def->try_accept(); }); }); } void ConfigDefinition::validate_all_options() { for (auto& v : options_validators) v(); } void ConfigDefinition::add_section_comments(const std::string& section, std::vector comments) { auto& sectionComments = section_comments[section]; for (auto& c : comments) sectionComments.emplace_back(std::move(c)); } void ConfigDefinition::add_option_comments( const std::string& section, const std::string& name, std::vector comments) { auto& defComments = definition_comments[section][name]; if (defComments.empty()) defComments = std::move(comments); else defComments.insert( defComments.end(), std::make_move_iterator(comments.begin()), std::make_move_iterator(comments.end())); } std::string ConfigDefinition::generate_ini_config(bool useValues) { std::string ini; auto ini_append = std::back_inserter(ini); int sectionsVisited = 0; visit_sections([&](const std::string& section, const DefinitionMap&) { std::string sect_str; auto sect_append = std::back_inserter(sect_str); visit_definitions(section, [&](const std::string& name, const std::unique_ptr& def) { bool has_comment = false; // TODO: as above, this will create empty objects // TODO: as above (but more important): this won't handle definitions with no // entries // (i.e. those handled by UndeclaredValueHandler's) for (const std::string& comment : definition_comments[section][name]) { fmt::format_to(sect_append, "\n# {}", comment); has_comment = true; } if (useValues and def->get_number_found() > 0) { for (const auto& val : def->values_as_string()) fmt::format_to(sect_append, "\n{}={}", name, val); *sect_append = '\n'; } else if (not def->hidden) { if (auto defaults = def->default_values_as_string(); 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()) return; // Skip sections with no options if (sectionsVisited > 0) ini += "\n\n"; fmt::format_to(ini_append, "[{}]\n", section); // TODO: this will create empty objects as a side effect of map's operator[] // TODO: this also won't handle sections which have no definition for (const std::string& comment : section_comments[section]) { fmt::format_to(ini_append, "# {}\n", comment); } ini += "\n"; ini += sect_str; sectionsVisited++; }); return ini; } const std::unique_ptr& ConfigDefinition::lookup_definition_or_throw( std::string_view section, std::string_view name) const { const auto sectionItr = definitions.find(std::string(section)); if (sectionItr == definitions.end()) throw std::invalid_argument{"No config section [{}]"_format(section)}; auto& sectionDefinitions = sectionItr->second; const auto definitionItr = sectionDefinitions.find(std::string(name)); if (definitionItr == sectionDefinitions.end()) throw std::invalid_argument{"No config item {} within section {}"_format(name, section)}; return definitionItr->second; } std::unique_ptr& ConfigDefinition::lookup_definition_or_throw( std::string_view section, std::string_view name) { return const_cast&>( const_cast(this)->lookup_definition_or_throw(section, name)); } void ConfigDefinition::visit_sections(SectionVisitor visitor) const { for (const std::string& section : section_ordering) { const auto itr = definitions.find(section); assert(itr != definitions.end()); visitor(section, itr->second); } }; void ConfigDefinition::visit_definitions(const std::string& section, DefVisitor visitor) const { const auto& defs = definitions.at(section); const auto& defOrdering = definition_ordering.at(section); for (const std::string& name : defOrdering) { const auto itr = defs.find(name); assert(itr != defs.end()); visitor(name, itr->second); } }; } // namespace llarp ================================================ FILE: llarp/config/definition.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace llarp { namespace config { enum class Type { Relay, FullClient, EmbeddedClient }; inline constexpr std::string_view to_string(Type t) { switch (t) { case Type::Relay: return "service node"; case Type::FullClient: return "client"; case Type::EmbeddedClient: return "embedded client"; } return "unknown config type"; } namespace flag { // Base class for the following option flag types struct opt {}; struct REQUIRED : opt {}; struct HIDDEN : opt {}; struct MULTIVALUE : opt {}; struct NOTEMBEDDED : opt {}; struct RELAYONLY : NOTEMBEDDED {}; struct CLIENTONLY : opt {}; struct FULLCLIENTONLY : CLIENTONLY, NOTEMBEDDED {}; struct DEPRECATED : opt {}; } // namespace flag /// Value to pass for an OptionDefinition to indicate that the option is required inline constexpr flag::REQUIRED Required{}; /// Value to pass for an OptionDefinition to indicate that the option should be hidden from /// the generate config file if it is unset (and has no comment). Typically for deprecated, /// renamed options that still do something, and for internal dev options that aren't /// usefully exposed. (For do-nothing deprecated options use Deprecated instead). inline constexpr flag::HIDDEN Hidden{}; /// Value to pass for an OptionDefinition to indicate that the option takes multiple values inline constexpr flag::MULTIVALUE MultiValue{}; /// Value to pass for an option that should only be set for relay configs. If found in a /// client config it be ignored (but will produce a warning). inline constexpr flag::RELAYONLY RelayOnly{}; /// Value to pass for an option that should only be set for client configs. If found in a /// relay config it will be ignored (but will produce a warning). inline constexpr flag::CLIENTONLY ClientOnly{}; /// Value to pass for an option that should only be set for full client configs but not /// relay configs or embedded client configs. If found in either of this it will be ignored /// (but will produce a warning). inline constexpr flag::FULLCLIENTONLY FullClientOnly{}; /// Value to pass for an option that is not allowed in embedded configs. This is implied by /// RelayOnly and FullClientOnly, but can be specified separately for options supported in /// both full clients and relays but not embedded configs. If such an option is specified /// for an embedded client config it will be ignored (but will produce a warning). inline constexpr flag::NOTEMBEDDED NotEmbedded{}; /// Value to pass for an option that is deprecated and does nothing and should be ignored /// (with a deprecation warning) if specified. Note that Deprecated implies Hidden, and /// that {client,relay}-only options in a {relay,client} config are also considered /// Deprecated. inline constexpr flag::DEPRECATED Deprecated{}; /// Wrapper to specify a default value to an OptionDefinition template struct Default { T val; constexpr explicit Default(T val) : val{std::move(val)} {} }; /// Adds one or more comment lines to the option definition. struct Comment { std::vector comments; explicit Comment(std::initializer_list comments) : comments{std::move(comments)} {} }; /// A convenience function that returns an acceptor which assigns to a reference. /// /// Note that this holds on to the reference; it must only be used when this is safe to do. /// In particular, a reference to a local variable may be problematic. template auto assignment_acceptor(T& ref) { return [&ref](T arg) { ref = std::move(arg); }; } /// Returns a value acceptor that accepts any value within a range of values. template auto bounded_assignment_acceptor( T& ref, std::type_identity_t min, std::type_identity_t max, std::string setting_name) { return [&ref, min = std::move(min), max = std::move(max), name = std::move(setting_name)](T arg) { if (arg < min || arg > max) throw std::invalid_argument{fmt::format("{} must be >= {} and <= {}", name, min, max)}; ref = std::move(arg); }; } template auto lower_bounded_assignment_acceptor(T& ref, std::type_identity_t min, std::string setting_name) { return [&ref, min = std::move(min), name = std::move(setting_name)](T arg) { if (arg < min) throw std::invalid_argument{fmt::format("{} must be >= {}", name, min)}; ref = std::move(arg); }; } template auto upper_bounded_assignment_acceptor(T& ref, std::type_identity_t max, std::string setting_name) { return [&ref, max = std::move(max), name = std::move(setting_name)](T arg) { if (arg > max) throw std::invalid_argument{fmt::format("{} must be <= {}", name, max)}; ref = std::move(arg); }; } template auto bounded_assignment_acceptor( std::optional& ref, std::type_identity_t min, std::type_identity_t max, std::string setting_name) { return [&ref, min = std::move(min), max = std::move(max), name = std::move(setting_name)](T arg) { if (arg < min || arg > max) throw std::invalid_argument{fmt::format("{} must be >= {} and <= {}", name, min, max)}; ref = std::move(arg); }; } template constexpr bool is_default = false; template constexpr bool is_default> = true; template constexpr bool is_default = is_default>; template constexpr bool is_default_array = false; template constexpr bool is_default_array, N>> = true; template constexpr bool is_default_array = is_default_array>; template concept is_option = std::is_base_of_v> or std::is_same_v or is_default