Merge pull request #1945 from necro-nemesis/fedora/35

v0.9.9 update
fedora/35
majestrate 2 years ago committed by GitHub
commit 167d24ff04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

7
.gitmodules vendored

@ -17,9 +17,6 @@
path = external/pybind11
url = https://github.com/pybind/pybind11
branch = stable
[submodule "external/clang-format-hooks"]
path = external/clang-format-hooks
url = https://github.com/barisione/clang-format-hooks/
[submodule "external/sqlite_orm"]
path = external/sqlite_orm
url = https://github.com/fnc12/sqlite_orm
@ -35,3 +32,7 @@
[submodule "external/ngtcp2"]
path = external/ngtcp2
url = https://github.com/ngtcp2/ngtcp2.git
branch = v0.1.0
[submodule "external/oxen-encoding"]
path = external/oxen-encoding
url = https://github.com/oxen-io/oxen-encoding.git

@ -5,8 +5,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Has to be set before `project()`, and ignored on non-macos:
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)")
option(BUILD_DAEMON "build lokinet daemon and associated utils" ON)
set(LANGS C CXX)
if(APPLE)
if(APPLE AND BUILD_DAEMON)
set(LANGS ${LANGS} OBJC Swift)
endif()
@ -22,7 +25,7 @@ endif()
project(lokinet
VERSION 0.9.8
VERSION 0.9.9
DESCRIPTION "lokinet - IP packet onion router"
LANGUAGES ${LANGS})
@ -32,16 +35,15 @@ if(APPLE)
set(LOKINET_APPLE_BUILD 0)
endif()
set(RELEASE_MOTTO "A Series of Tubes" CACHE STRING "Release motto")
set(RELEASE_MOTTO "Gluten Free Edition" CACHE STRING "Release motto")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
add_definitions(-DLLARP_VERSION_MAJOR=${lokinet_VERSION_MAJOR})
add_definitions(-DLLARP_VERSION_MINOR=${lokinet_VERSION_MINOR})
add_definitions(-DLLARP_VERSION_PATCH=${lokinet_VERSION_PATCH})
if(RELEASE_MOTTO AND CMAKE_BUILD_TYPE MATCHES "[Rr][Ee][Ll][Ee][Aa][Ss][Ee]")
add_definitions(-DLLARP_RELEASE_MOTTO="${RELEASE_MOTTO}")
set(DEFAULT_WITH_BOOTSTRAP ON)
if(APPLE)
set(DEFAULT_WITH_BOOTSTRAP OFF)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
# Core options
option(USE_AVX2 "enable avx2 code" OFF)
@ -57,12 +59,16 @@ option(WITH_COVERAGE "generate coverage data" OFF)
option(USE_SHELLHOOKS "enable shell hooks on compile time (dangerous)" OFF)
option(WARNINGS_AS_ERRORS "treat all warnings as errors. turn off for development, on for release" OFF)
option(TRACY_ROOT "include tracy profiler source" OFF)
option(WITH_TESTS "build unit tests" ON)
option(WITH_TESTS "build unit tests" OFF)
option(WITH_HIVE "build simulation stubs" OFF)
option(BUILD_PACKAGE "builds extra components for making an installer (with 'make package')" OFF)
option(WITH_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP})
include(cmake/enable_lto.cmake)
option(CROSS_PLATFORM "cross compiler platform" "Linux")
option(CROSS_PREFIX "toolchain cross compiler prefix" "")
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)
@ -95,7 +101,6 @@ include(cmake/target_link_libraries_system.cmake)
include(cmake/add_import_library.cmake)
include(cmake/add_log_tag.cmake)
include(cmake/libatomic.cmake)
include(cmake/link_dep_libs.cmake)
if (STATIC_LINK)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX})
@ -170,19 +175,36 @@ if(NOT TARGET sodium)
export(TARGETS sodium NAMESPACE sodium:: FILE sodium-exports.cmake)
endif()
option(FORCE_OXENC_SUBMODULE "force using oxen-encoding submodule" OFF)
if(NOT FORCE_OXENC_SUBMODULE)
pkg_check_modules(OXENC liboxenc>=1.0.3 IMPORTED_TARGET)
endif()
if(OXENC_FOUND)
if(NOT TARGET PkgConfig::OXENC AND CMAKE_VERSION VERSION_LESS "3.21")
# Work around cmake bug 22180 (PkgConfig::OXENC not set if no flags needed):
add_library(_empty_oxenc INTERFACE)
add_library(oxenc::oxenc ALIAS _empty_oxenc)
else()
add_library(oxenc::oxenc ALIAS PkgConfig::OXENC)
endif()
message(STATUS "Found system liboxenc ${OXENC_VERSION}")
else()
message(STATUS "using oxen-encoding submodule")
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/oxen-encoding)
add_library(oxenc::oxenc ALIAS oxenc)
endif()
option(FORCE_OXENMQ_SUBMODULE "force using oxenmq submodule" OFF)
if(NOT FORCE_OXENMQ_SUBMODULE)
pkg_check_modules(OXENMQ liboxenmq>=1.2.4)
pkg_check_modules(OXENMQ liboxenmq>=1.2.12 IMPORTED_TARGET)
endif()
if(OXENMQ_FOUND)
add_library(oxenmq INTERFACE)
link_dep_libs(oxenmq INTERFACE "${OXENMQ_LIBRARY_DIRS}" ${OXENMQ_LIBRARIES})
target_include_directories(oxenmq INTERFACE ${OXENMQ_INCLUDE_DIRS})
add_library(oxenmq::oxenmq ALIAS oxenmq)
add_library(oxenmq::oxenmq ALIAS PkgConfig::OXENMQ)
message(STATUS "Found system liboxenmq ${OXENMQ_VERSION}")
else()
message(STATUS "using oxenmq submodule")
add_subdirectory(${CMAKE_SOURCE_DIR}/external/oxen-mq)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/oxen-mq)
endif()
@ -327,8 +349,9 @@ endif()
add_subdirectory(crypto)
add_subdirectory(llarp)
add_subdirectory(daemon)
if(BUILD_DAEMON)
add_subdirectory(daemon)
endif()
if(WITH_HIVE)
add_subdirectory(pybind)

@ -1,16 +1,3 @@
LokiNET is the reference implementation of LLARP (Low Latency Anonymous
Routing Protocol).
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.
Copyright (c) 2018-2020 The Loki Project
Copyright (c) 2018-2020 Jeff Becker
Windows NT port and portions Copyright (c) 2018-2020 Rick V.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@ -684,4 +671,4 @@ 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
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<https://www.gnu.org/licenses/why-not-lgpl.html>.

@ -1,8 +1,17 @@
From cdad3e7f093c4b0c69f73580e4fbacc24067db96 Mon Sep 17 00:00:00 2001
From: necro-nemsis <necro_nemesis@hotmail.com>
Date: Sun, 19 Jun 2022 06:47:01 -0400
Subject: [PATCH] Change default port to 953
---
llarp/config/config.cpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp
index 78d152602..8b07b0cec 100644
index 6bff9611..9c89bce6 100644
--- a/llarp/config/config.cpp
+++ b/llarp/config/config.cpp
@@ -703,7 +703,10 @@ namespace llarp
@@ -760,7 +760,10 @@ namespace llarp
// can bind to other 127.* IPs to avoid conflicting with something else that may be listening on
// 127.0.0.1:53.
#ifdef __linux__
@ -14,3 +23,5 @@ index 78d152602..8b07b0cec 100644
#else
constexpr Default DefaultDNSBind{"127.0.0.1:53"};
#endif
--
2.30.2

@ -1,28 +0,0 @@
From: Jason Rhinelander <jason@imaginary.ca>
Date: Fri, 13 Dec 2019 17:23:41 -0400
Subject: Pass debian version as GIT_VERSION
---
cmake/Version.cmake | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/cmake/Version.cmake b/cmake/Version.cmake
index 45037a0..d9fdaef 100644
--- a/cmake/Version.cmake
+++ b/cmake/Version.cmake
@@ -1,4 +1,8 @@
+if(GIT_VERSION)
+ set(VERSIONTAG "${GIT_VERSION}")
+ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp")
+else()
find_package(Git QUIET)
set(GIT_INDEX_FILE "${PROJECT_SOURCE_DIR}/.git/index")
if(EXISTS ${GIT_INDEX_FILE} AND ( GIT_FOUND OR Git_FOUND) )
@@ -18,5 +22,6 @@ else()
set(VERSIONTAG "nogit")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp")
endif()
+endif()
add_custom_target(genversion DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp")

@ -1,5 +1,5 @@
Name: lokinet
Version: 0.9.8
Version: 0.9.9
Release: 1%{?dist}
Summary: Lokinet anonymous, decentralized overlay network
@ -13,7 +13,6 @@ BuildRequires: cmake
BuildRequires: gcc-c++
BuildRequires: pkgconfig
BuildRequires: libuv-devel
BuildRequires: oxenmq-devel
BuildRequires: unbound-devel
BuildRequires: libsodium-devel
BuildRequires: systemd-devel
@ -22,11 +21,9 @@ BuildRequires: libcurl-devel
BuildRequires: jemalloc-devel
BuildRequires: libsqlite3x-devel
# Puts the rpm version instead of the git tag in the version string:
Patch1: version-as-rpm-version.patch
# Changes the default dns listener to 127.0.0.1:953 because Fedora's systemd-resolved doesn't like
# talking to 127.3.2.1:53 for unknown reasons.
Patch2: default-dns.patch
Patch1: default-dns.patch
Requires: lokinet-bin = %{version}-%{release}
%{?systemd_requires}
@ -84,7 +81,7 @@ export CFLAGS="%{optflags} -march=armv6 -mtune=cortex-a53 -mfloat-abi=hard -mfpu
%endif
%undefine __cmake_in_source_build
%cmake -DNATIVE_BUILD=OFF -DUSE_AVX2=OFF -DWITH_TESTS=OFF %{cmake_extra_args} -DCMAKE_BUILD_TYPE=Release -DGIT_VERSION="%{release}" -DWITH_SETCAP=OFF -DSUBMODULE_CHECK=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_LIBLOKINET=OFF
%cmake -DFORCE_OXENC_SUBMODULE=ON -DFORCE_OXENMQ_SUBMODULE=ON -DNATIVE_BUILD=OFF -DUSE_AVX2=OFF -DWITH_TESTS=OFF %{cmake_extra_args} -DCMAKE_BUILD_TYPE=Release -DLOKINET_VERSIONTAG=%{release} -DWITH_SETCAP=OFF -DSUBMODULE_CHECK=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_LIBLOKINET=OFF
%cmake_build
%install
@ -100,7 +97,7 @@ install -Dm644 SOURCES/bootstrap.signed $RPM_BUILD_ROOT%{_sharedstatedir}/lokine
%files
%license LICENSE.txt
%license LICENSE
%doc readme.*
%{_datadir}/polkit-1/rules.d/50-lokinet.rules
%{_unitdir}/lokinet.service
@ -156,6 +153,12 @@ fi
%systemd_postun lokinet.service
%changelog
* Wed Jun 29 2022 Technical Tumbleweed <necro_nemesis@hotmail.com> - 0.9.9-1
- bump version
- cmake flags for no system library search
- update port patch
- remove version patch
* Wed Nov 17 2021 Technical Tumbleweed <necro_nemesis@hotmail.com> - 0.9.8-1
- bump version

@ -57,4 +57,4 @@ else()
endif()
endif()
configure_file("${SRC}" "${DEST}")
configure_file("${SRC}" "${DEST}" @ONLY)

@ -5,32 +5,32 @@
set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads")
set(OPENSSL_VERSION 1.1.1l CACHE STRING "openssl version")
set(OPENSSL_VERSION 1.1.1o CACHE STRING "openssl version")
set(OPENSSL_MIRROR ${LOCAL_MIRROR} https://www.openssl.org/source CACHE STRING "openssl download mirror(s)")
set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz)
set(OPENSSL_HASH SHA256=0b7a3e5e59c34827fe0c3a74b7ec8baef302b98fa80088d7f9153aa16fa76bd1
set(OPENSSL_HASH SHA256=9384a2b0570dd80358841464677115df785edb941c71211f75076d72fe6b438f
CACHE STRING "openssl source hash")
set(EXPAT_VERSION 2.3.0 CACHE STRING "expat version")
set(EXPAT_VERSION 2.4.8 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=dde8a9a094b18d795a0e86ca4aa68488b352dc67019e0d669e8b910ed149628de4c2a49bc3a5b832f624319336a01f9e4debe03433a43e1c420f36356d886820
set(EXPAT_HASH SHA256=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25
CACHE STRING "expat source hash")
set(UNBOUND_VERSION 1.13.2 CACHE STRING "unbound version")
set(UNBOUND_VERSION 1.15.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 SHA256=0a13b547f3b92a026b5ebd0423f54c991e5718037fd9f72445817f6a040e1a83
set(UNBOUND_HASH SHA256=a480dc6c8937447b98d161fe911ffc76cfaffa2da18788781314e81339f1126f
CACHE STRING "unbound source hash")
set(SQLITE3_VERSION 3350500 CACHE STRING "sqlite3 version")
set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2021
set(SQLITE3_VERSION 3380500 CACHE STRING "sqlite3 version")
set(SQLITE3_MIRROR ${LOCAL_MIRROR} https://www.sqlite.org/2022
CACHE STRING "sqlite3 download mirror(s)")
set(SQLITE3_SOURCE sqlite-autoconf-${SQLITE3_VERSION}.tar.gz)
set(SQLITE3_HASH SHA512=039af796f79fc4517be0bd5ba37886264d49da309e234ae6fccdb488ef0109ed2b917fc3e6c1fc7224dff4f736824c653aaf8f0a37550c5ebc14d035cb8ac737
CACHE STRING "sqlite3 source hash")
set(SQLITE3_HASH SHA3_256=ab649fea76f49a6ec7f907f001d87b8bd76dec0679c783e3992284c5a882a98c
CACHE STRING "sqlite3 source hash")
set(SODIUM_VERSION 1.0.18 CACHE STRING "libsodium version")
set(SODIUM_MIRROR ${LOCAL_MIRROR}
@ -48,29 +48,27 @@ set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz)
set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516bcf284d241d4616cad3cb4ad9af8c10373d456de92dc6d115b037941659f141e7c0e
CACHE STRING "libzmq source hash")
set(LIBUV_VERSION 1.41.0 CACHE STRING "libuv version")
set(LIBUV_VERSION 1.44.1 CACHE STRING "libuv version")
set(LIBUV_MIRROR ${LOCAL_MIRROR} https://dist.libuv.org/dist/v${LIBUV_VERSION}
CACHE STRING "libuv mirror(s)")
set(LIBUV_SOURCE libuv-v${LIBUV_VERSION}.tar.gz)
set(LIBUV_HASH SHA512=33613fa28e8136507300eba374351774849b6b39aab4e53c997a918d3bc1d1094c6123e0e509535095b14dc5daa885eadb1a67bed46622ad3cc79d62dc817e84
set(LIBUV_HASH SHA512=b4f8944e2c79e3a6a31ded6cccbe4c0eeada50db6bc8a448d7015642795012a4b80ffeef7ca455bb093c59a8950d0e1430566c3c2fa87b73f82699098162d834
CACHE STRING "libuv source hash")
set(ZLIB_VERSION 1.2.11 CACHE STRING "zlib version")
set(ZLIB_VERSION 1.2.12 CACHE STRING "zlib version")
set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net
CACHE STRING "zlib mirror(s)")
set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.gz)
set(ZLIB_HASH SHA512=73fd3fff4adeccd4894084c15ddac89890cd10ef105dd5e1835e1e9bbb6a49ff229713bd197d203edfa17c2727700fce65a2a235f07568212d820dca88b528ae
CACHE STRING "zlib source hash")
set(ZLIB_HASH SHA256=91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9
CACHE STRING "zlib source hash")
set(CURL_VERSION 7.76.1 CACHE STRING "curl version")
set(CURL_VERSION 7.83.1 CACHE STRING "curl version")
set(CURL_MIRROR ${LOCAL_MIRROR} https://curl.haxx.se/download https://curl.askapache.com
CACHE STRING "curl mirror(s)")
set(CURL_SOURCE curl-${CURL_VERSION}.tar.xz)
set(CURL_HASH SHA256=64bb5288c39f0840c07d077e30d9052e1cbb9fa6c2dc52523824cc859e679145
set(CURL_HASH SHA256=2cb9c2356e7263a1272fd1435ef7cdebf2cd21400ec287b068396deb705c22c4
CACHE STRING "curl source hash")
include(ExternalProject)
set(DEPS_DESTDIR ${CMAKE_BINARY_DIR}/static-deps)
@ -236,6 +234,7 @@ add_static_target(zlib zlib_external libz.a)
set(openssl_system_env "")
set(openssl_configure_command ./config)
if(CMAKE_CROSSCOMPILING)
if(ARCH_TRIPLET STREQUAL x86_64-w64-mingw32)
set(openssl_system_env SYSTEM=MINGW64 RC=${CMAKE_RC_COMPILER} AR=${ARCH_TRIPLET}-ar RANLIB=${ARCH_TRIPLET}-ranlib)
@ -244,13 +243,31 @@ if(CMAKE_CROSSCOMPILING)
elseif(ANDROID)
set(openssl_system_env SYSTEM=Linux MACHINE=${android_machine} LD=${deps_ld} RANLIB=${deps_ranlib} AR=${deps_ar})
set(openssl_extra_opts no-asm)
elseif(ARCH_TRIPLET STREQUAL mips64-linux-gnuabi64)
set(openssl_system_env SYSTEM=Linux MACHINE=mips64)
set(openssl_configure_command ./Configure linux64-mips64)
elseif(ARCH_TRIPLET STREQUAL mips-linux-gnu)
set(openssl_system_env SYSTEM=Linux MACHINE=mips)
elseif(ARCH_TRIPLET STREQUAL mipsel-linux-gnu)
set(openssl_system_env SYSTEM=Linux MACHINE=mipsel)
elseif(ARCH_TRIPLET STREQUAL aarch64-linux-gnu)
# cross compile arm64
set(openssl_system_env SYSTEM=Linux MACHINE=aarch64)
elseif(ARCH_TRIPLET MATCHES arm-linux)
# cross compile armhf
set(openssl_system_env SYSTEM=Linux MACHINE=armv4)
elseif(ARCH_TRIPLET MATCHES powerpc64le)
# cross compile ppc64le
set(openssl_system_env SYSTEM=Linux MACHINE=ppc64le)
endif()
elseif(CMAKE_C_FLAGS MATCHES "-march=armv7")
# Help openssl figure out that we're building from armv7 even if on armv8 hardware:
set(openssl_system_env SYSTEM=Linux MACHINE=armv7)
endif()
build_external(openssl
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${deps_cc} ${openssl_system_env} ./config
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CC=${deps_cc} ${openssl_system_env} ${openssl_configure_command}
--prefix=${DEPS_DESTDIR} ${openssl_extra_opts} no-shared no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost
no-heartbeats no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-ssl-trace no-ssl2 no-ssl3
no-static-engine no-tests no-weak-ssl-ciphers no-zlib no-zlib-dynamic "CFLAGS=${deps_CFLAGS}"
@ -277,7 +294,6 @@ build_external(expat
)
add_static_target(expat expat_external libexpat.a)
build_external(unbound
DEPENDS openssl_external expat_external
CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared
@ -335,6 +351,10 @@ set_target_properties(libzmq PROPERTIES
INTERFACE_LINK_LIBRARIES "${libzmq_link_libs}"
INTERFACE_COMPILE_DEFINITIONS "ZMQ_STATIC")
if(NOT WITH_BOOTSTRAP)
return()
endif()
set(curl_extra)
if(WIN32)
set(curl_ssl_opts --without-ssl --with-schannel)
@ -386,7 +406,7 @@ foreach(curl_arch ${curl_arches})
--enable-crypto-auth --disable-ntlm-wb --disable-tls-srp --disable-unix-sockets --disable-cookies
--enable-http-auth --enable-doh --disable-mime --enable-dateparse --disable-netrc --without-libidn2
--disable-progress-meter --without-brotli --with-zlib=${DEPS_DESTDIR} ${curl_ssl_opts}
--without-libmetalink --without-librtmp --disable-versioned-symbols --enable-hidden-symbols
--without-librtmp --disable-versioned-symbols --enable-hidden-symbols
--without-zsh-functions-dir --without-fish-functions-dir
--without-nghttp3 --without-zstd
"CC=${deps_cc}" "CFLAGS=${deps_noarch_CFLAGS}${cflags_extra}" ${curl_extra}

@ -1,22 +1,41 @@
# 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.
find_package(Git QUIET)
set(VERSIONTAG "${GIT_VERSION}")
set(GIT_INDEX_FILE "${PROJECT_SOURCE_DIR}/.git/index")
if(EXISTS ${GIT_INDEX_FILE} AND ( GIT_FOUND OR Git_FOUND) )
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}"
"-D" "GIT=${GIT_EXECUTABLE}"
${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()
message(STATUS "Git was not found! Setting version to to nogit")
set(VERSIONTAG "nogit")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/constants/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp" @ONLY)
endif()
add_custom_target(genversion DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp")
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)

@ -1,7 +1,7 @@
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.txt")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
if(WIN32)
include(cmake/win32_installer_deps.cmake)

@ -1,13 +0,0 @@
# Properly links a target to a list of library names by finding the given libraries. Takes:
# - a target
# - a linktype (e.g. INTERFACE, PUBLIC, PRIVATE)
# - a library search path (or "" for defaults)
# - any number of library names
function(link_dep_libs target linktype libdirs)
foreach(lib ${ARGN})
find_library(link_lib-${lib} NAMES ${lib} PATHS ${libdirs})
if(link_lib-${lib})
target_link_libraries(${target} ${linktype} ${link_lib-${lib}})
endif()
endforeach()
endfunction()

@ -45,8 +45,9 @@ function(add_ngtcp2_lib)
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 PRIVATE -DHAVE_CONFIG_H -D_GNU_SOURCE)
target_compile_definitions(ngtcp2_static PRIVATE -DHAVE_CONFIG_H -D_GNU_SOURCE)
endfunction()

@ -15,8 +15,9 @@ if(NOT MSVC_VERSION)
# to .r[o]data section one after the other!
add_compile_options(-fno-ident -Wa,-mbig-obj)
link_libraries( -lws2_32 -lshlwapi -ldbghelp -luser32 -liphlpapi -lpsapi -luserenv)
# zmq requires windows xp or higher
add_definitions(-DWINVER=0x0501 -D_WIN32_WINNT=0x0501)
# the minimum windows version, set to 6 rn because supporting older windows is hell
set(_winver 0x0600)
add_definitions(-DWINVER=${_winver} -D_WIN32_WINNT=${_winver})
endif()
if(EMBEDDED_CFG)

@ -1,6 +1,6 @@
if(NOT GUI_ZIP_URL)
set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/loki-network-control-panel/lokinet-gui-windows-32bit-v0.3.8.zip")
set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=60c2b738cf997e5684f307e5222498fd09143d495a932924105a49bf59ded8bb)
set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip")
set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=316f10489f5907bfa9c74b21f8ef2fdd7b7c7e6a0f5bcedaed2ee5f4004eab52)
endif()
set(TUNTAP_URL "https://build.openvpn.net/downloads/releases/latest/tap-windows-latest-stable.exe")
@ -27,14 +27,26 @@ set(CPACK_PACKAGE_INSTALL_DIRECTORY "Lokinet")
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/win32-setup/lokinet.ico")
set(CPACK_NSIS_DEFINES "RequestExecutionLevel admin")
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ifFileExists $INSTDIR\\\\bin\\\\tuntap-install.exe 0 +2\\nExecWait '$INSTDIR\\\\bin\\\\tuntap-install.exe /S'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --install'\\nExecWait 'sc failure lokinet reset= 60 actions= restart/1000'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe -g C:\\\\ProgramData\\\\lokinet\\\\lokinet.ini'\\nCopyFiles '$INSTDIR\\\\share\\\\bootstrap.signed' C:\\\\ProgramData\\\\lokinet\\\\bootstrap.signed\\n")
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'net stop lokinet'\\nExecWait 'taskkill /f /t /im lokinet-gui.exe'\\nExecWait '$INSTDIR\\\\bin\\\\lokinet.exe --remove'\\nRMDir /r /REBOOTOK C:\\\\ProgramData\\\\lokinet")
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Lokinet.lnk' '$INSTDIR\\\\share\\\\gui\\\\lokinet-gui.exe'"
)
set(CPACK_NSIS_DELETE_ICONS_EXTRA
"Delete '$SMPROGRAMS\\\\$START_MENU\\\\Lokinet.lnk'"
)
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}")
get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified")

@ -0,0 +1,54 @@
#!/bin/bash
set -e
set +x
default_abis="armeabi-v7a arm64-v8a x86_64"
build_abis=${ABIS:-$default_abis}
test x$NDK = x && echo "NDK env var not set"
test x$NDK = x && exit 1
echo "building abis: $build_abis"
root="$(readlink -f $(dirname $0)/../)"
build=$root/build-android
mkdir -p $build
cd $build
for abi in $build_abis; do
mkdir -p build-$abi
cd build-$abi
cmake \
-G 'Unix Makefiles' \
-DANDROID=ON \
-DANDROID_ABI=$abi \
-DANDROID_ARM_MODE=arm \
-DANDROID_PLATFORM=android-23 \
-DANDROID_STL=c++_static \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DBUILD_STATIC_DEPS=ON \
-DBUILD_PACKAGE=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \
-DFORCE_OXENMQ_SUBMODULE=ON \
-DFORCE_OXENC_SUBMODULE=ON \
-DSUBMODULE_CHECK=OFF \
-DWITH_LTO=OFF \
-DCMAKE_BUILD_TYPE=Release \
$@ $root
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
done

@ -2,50 +2,9 @@
set -e
set +x
default_abis="armeabi-v7a arm64-v8a x86 x86_64"
build_abis=${ABIS:-$default_abis}
test x$NDK = x && echo "NDK env var not set"
test x$NDK = x && exit 1
echo "building abis: $build_abis"
root="$(readlink -f $(dirname $0)/../)"
out=$root/lokinet-jni-$(git describe || echo unknown)
mkdir -p $out
mkdir -p $root/build-android
cd $root/build-android
for abi in $build_abis; do
mkdir -p build-$abi $out/$abi
cd build-$abi
cmake \
-G 'Unix Makefiles' \
-DANDROID=ON \
-DANDROID_ABI=$abi \
-DANDROID_ARM_MODE=arm \
-DANDROID_PLATFORM=android-23 \
-DANDROID_STL=c++_static \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DBUILD_STATIC_DEPS=ON \
-DBUILD_PACKAGE=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \
-DFORCE_OXENMQ_SUBMODULE=ON \
-DSUBMODULE_CHECK=OFF \
-DWITH_LTO=OFF \
-DCMAKE_BUILD_TYPE=Release \
$@ $root
make lokinet-android -j${JOBS:-$(nproc)}
cp jni/liblokinet-android.so $out/$abi/liblokinet-android.so
cd -
done
echo
echo "build artifacts outputted to $out"
cd "$root"
./contrib/android-configure.sh $@
make -C build-android -j ${JOBS:-$(nproc)}

@ -1 +0,0 @@
lokinet-bootserv

@ -1,29 +0,0 @@
# replace your.server.tld with your server's fqdn
server {
listen 80;
server_name your.server.tld;
location / {
return 302 https://your.server.tld$request_uri;
}
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
}
server {
listen 443 ssl;
server_name your.server.tld;
ssl_certificate /etc/letsencrypt/live/your.server.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.server.tld/privkey.pem;
location / {
root /var/www/lokinet-bootserv;
}
location /bootstrap.signed {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/bin/lokinet-bootserv;
fastcgi_pass unix://tmp/cgi.sock;
}
}

@ -1,4 +0,0 @@
# set me to where the nodedb is for lokinet
#[nodedb]
#dir=/path/to/nodedb/

@ -1,20 +0,0 @@
SRC = $(sort $(wildcard src/*.cpp))
OBJS = $(SRC:.cpp=.cpp.o)
CGI_EXE = lokinet-bootserv
CXX = clang++
all: build
build: $(CGI_EXE)
%.cpp.o: %.cpp
$(CXX) -g -std=c++17 -c -Wall -Werror -Wpedantic $^ -o $^.o
$(CGI_EXE): $(OBJS)
$(CXX) -o $(CGI_EXE) $^
clean:
rm -f $(CGI_EXE) $(OBJS)

@ -1,35 +0,0 @@
# lokinet-bootserv
cgi executable for serving a random RC for bootstrap from a nodedb
## configuring
copy the example config (privileged)
# cp configs/lokinet-bootserv.ini /usr/local/etc/lokinet-bootserv.ini
edit config to have proper values,
specifically make sure the `[nodedb]` section has a `dir` value that points to a static copy of a healthy nodedb
## building
to build:
$ make
## installing (priviledged)
install cgi binary:
# cp lokinet-bootserv /usr/local/bin/lokinet-bootserv
set up with nginx cgi:
# cp configs/lokinet-bootserv-nginx.conf /etc/nginx/sites-available/lokinet-bootserv.conf
# ln -s /etc/nginx/sites-available/lokinet-bootserv.conf /etc/nginx/sites-enabled/
## maintainence
add the following to crontab
0 0 * * * /usr/local/bin/lokinet-bootserv --cron

@ -1,171 +0,0 @@
#include "lokinet-cgi.hpp"
#include <fstream>
#include <dirent.h>
#include <list>
#include <sstream>
namespace lokinet
{
namespace bootserv
{
CGIHandler::CGIHandler(std::ostream& o) : Handler(o)
{
}
CGIHandler::~CGIHandler()
{
}
int
CGIHandler::Exec(const Config& conf)
{
const char* e = getenv("REQUEST_METHOD");
if(e == nullptr)
return ReportError("$REQUEST_METHOD not set");
std::string_view method(e);
if(method != "GET")
{
out << "Content-Type: text/plain" << std::endl;
out << "Status: 405 Method Not Allowed" << std::endl << std::endl;
return 0;
}
std::string fname;
if(!conf.VisitSection(
"nodedb", [&](const Config::Section_t& sect) -> bool {
auto itr = sect.find("dir");
if(itr == sect.end())
return false;
fname = PickRandomFileInDir(
std::string(itr->second.data(), itr->second.size()));
return true;
}))
return ReportError("bad values in nodedb section of config");
if(fname.empty())
{
// no files in nodedb
out << "Content-Type: text/plain" << std::endl;
out << "Status: 404 Not Found" << std::endl << std::endl;
return 0;
}
return ServeFile(fname.c_str(), "application/octect-stream");
}
std::string
CGIHandler::PickRandomFileInDir(std::string dirname) const
{
// collect files
std::list< std::string > files;
{
DIR* d = opendir(dirname.c_str());
if(d == nullptr)
{
return "";
};
std::list< std::string > subdirs;
dirent* ent = nullptr;
while((ent = readdir(d)))
{
std::string_view f = ent->d_name;
if(f != "." && f != "..")
{
std::stringstream ss;
ss << dirname;
ss << '/';
ss << f;
subdirs.emplace_back(ss.str());
}
}
closedir(d);
for(const auto& subdir : subdirs)
{
d = opendir(subdir.c_str());
if(d)
{
while((ent = readdir(d)))
{
std::string_view f;
f = ent->d_name;
if(f != "." && f != ".."
&& f.find_last_of(".signed") != std::string_view::npos)
{
std::stringstream ss;
ss << subdir << "/" << f;
files.emplace_back(ss.str());
}
}
closedir(d);
}
}
}
uint32_t randint;
{
std::basic_ifstream< uint32_t > randf("/dev/urandom");
if(!randf.is_open())
return "";
randf.read(&randint, 1);
}
auto itr = files.begin();
if(files.size() > 1)
std::advance(itr, randint % files.size());
return *itr;
}
int
CGIHandler::ServeFile(const char* fname, const char* contentType) const
{
std::ifstream f(fname);
if(f.is_open())
{
f.seekg(0, std::ios::end);
auto sz = f.tellg();
f.seekg(0, std::ios::beg);
if(sz)
{
out << "Content-Type: " << contentType << std::endl;
out << "Status: 200 OK" << std::endl;
out << "Content-Length: " << std::to_string(sz) << std::endl
<< std::endl;
char buf[512] = {0};
size_t r = 0;
while((r = f.readsome(buf, sizeof(buf))) > 0)
out.write(buf, r);
out << std::flush;
}
else
{
out << "Content-Type: text/plain" << std::endl;
out << "Status: 500 Internal Server Error" << std::endl << std::endl;
out << "could not serve '" << fname << "' as it is an empty file"
<< std::endl;
}
}
else
{
out << "Content-Type: text/plain" << std::endl;
out << "Status: 404 Not Found" << std::endl << std::endl;
out << "could not serve '" << fname
<< "' as it does not exist on the filesystem" << std::endl;
}
return 0;
}
int
CGIHandler::ReportError(const char* err)
{
out << "Content-Type: text/plain" << std::endl;
out << "Status: 500 Internal Server Error" << std::endl << std::endl;
out << err << std::endl;
return 0;
}
Handler_ptr
NewCGIHandler(std::ostream& out)
{
return std::make_unique< CGIHandler >(out);
}
} // namespace bootserv
} // namespace lokinet

@ -1,43 +0,0 @@
#ifndef LOKINET_BOOTSERV_HANDLER_HPP
#define LOKINET_BOOTSERV_HANDLER_HPP
#include <iostream>
#include "lokinet-config.hpp"
namespace lokinet
{
namespace bootserv
{
struct Handler
{
Handler(std::ostream& o) : out(o){};
virtual ~Handler(){};
/// handle command
/// return exit code
virtual int
Exec(const Config& conf) = 0;
/// report an error to system however that is done
/// return exit code
virtual int
ReportError(const char* err) = 0;
protected:
std::ostream& out;
};
using Handler_ptr = std::unique_ptr< Handler >;
/// create cgi handler
Handler_ptr
NewCGIHandler(std::ostream& out);
/// create cron handler
Handler_ptr
NewCronHandler(std::ostream& out);
} // namespace bootserv
} // namespace lokinet
#endif

@ -1,31 +0,0 @@
#ifndef BOOTSERV_LOKINET_CRON_HPP
#define BOOTSERV_LOKINET_CRON_HPP
#include "handler.hpp"
namespace lokinet
{
namespace bootserv
{
struct CGIHandler final : public Handler
{
CGIHandler(std::ostream& o);
~CGIHandler();
int
Exec(const Config& conf) override;
int
ReportError(const char* err) override;
int
ServeFile(const char* fname, const char* mime) const;
std::string
PickRandomFileInDir(std::string dirname) const;
};
} // namespace bootserv
} // namespace lokinet
#endif

@ -1,132 +0,0 @@
#include "lokinet-config.hpp"
#include <fstream>
#include <list>
#include <iostream>
namespace lokinet
{
namespace bootserv
{
const char* Config::DefaultPath = "/usr/local/etc/lokinet-bootserv.ini";
bool
Config::LoadFile(const char* fname)
{
{
std::ifstream f(fname);
if(!f.is_open())
return false;
f.seekg(0, std::ios::end);
m_Data.resize(f.tellg());
f.seekg(0, std::ios::beg);
if(m_Data.size() == 0)
return false;
f.read(m_Data.data(), m_Data.size());
}
return Parse();
}
void
Config::Clear()
{
m_Config.clear();
m_Data.clear();
}
bool
Config::Parse()
{
std::list< String_t > lines;
{
auto itr = m_Data.begin();
// split into lines
while(itr != m_Data.end())
{
auto beg = itr;
while(itr != m_Data.end() && *itr != '\n' && *itr != '\r')
++itr;
lines.emplace_back(std::addressof(*beg), (itr - beg));
if(itr == m_Data.end())
break;
++itr;
}
}
String_t sectName;
for(const auto& line : lines)
{
String_t realLine;
auto comment = line.find_first_of(';');
if(comment == String_t::npos)
comment = line.find_first_of('#');
if(comment == String_t::npos)
realLine = line;
else
realLine = line.substr(0, comment);
// blank or commented line?
if(realLine.size() == 0)
continue;
// find delimiters
auto sectOpenPos = realLine.find_first_of('[');
auto sectClosPos = realLine.find_first_of(']');
auto kvDelim = realLine.find_first_of('=');
if(sectOpenPos != String_t::npos && sectClosPos != String_t::npos
&& kvDelim == String_t::npos)
{
// section header
// clamp whitespaces
++sectOpenPos;
while(std::isspace(realLine[sectOpenPos])
&& sectOpenPos != sectClosPos)
++sectOpenPos;
--sectClosPos;
while(std::isspace(realLine[sectClosPos])
&& sectClosPos != sectOpenPos)
--sectClosPos;
// set section name
sectName = realLine.substr(sectOpenPos, sectClosPos);
}
else if(kvDelim != String_t::npos)
{
// key value pair
String_t::size_type k_start = 0;
String_t::size_type k_end = kvDelim;
String_t::size_type v_start = kvDelim + 1;
String_t::size_type v_end = realLine.size() - 1;
// clamp whitespaces
while(std::isspace(realLine[k_start]) && k_start != kvDelim)
++k_start;
while(std::isspace(realLine[k_end]) && k_end != k_start)
--k_end;
while(std::isspace(realLine[v_start]) && v_start != v_end)
++v_start;
while(std::isspace(realLine[v_end]))
--v_end;
// sect.k = v
String_t k = realLine.substr(k_start, k_end);
String_t v = realLine.substr(v_start, v_end);
Section_t& sect = m_Config[sectName];
sect[k] = v;
}
else // malformed?
return false;
}
return true;
}
bool
Config::VisitSection(
const char* name,
std::function< bool(const Section_t& sect) > visit) const
{
auto itr = m_Config.find(name);
if(itr == m_Config.end())
return false;
return visit(itr->second);
}
} // namespace bootserv
} // namespace lokinet

@ -1,47 +0,0 @@
#ifndef LOKINET_BOOTSERV_CONFIG_HPP
#define LOKINET_BOOTSERV_CONFIG_HPP
#include <unordered_map>
#include <string_view>
#include <functional>
#include <memory>
#include <vector>
namespace lokinet
{
namespace bootserv
{
struct Config
{
using String_t = std::string_view;
using Section_t = std::unordered_map< String_t, String_t >;
using Config_impl_t = std::unordered_map< String_t, Section_t >;
static const char* DefaultPath;
/// clear config
void
Clear();
/// load config file for bootserv
/// return true on success
/// return false on error
bool
LoadFile(const char* fname);
/// visit a section in config read only by name
/// return false if no section or value propagated from visitor
bool
VisitSection(const char* name,
std::function< bool(const Section_t&) > visit) const;
private:
bool
Parse();
std::vector< char > m_Data;
Config_impl_t m_Config;
};
} // namespace bootserv
} // namespace lokinet
#endif

@ -1,37 +0,0 @@
#include "lokinet-cron.hpp"
namespace lokinet
{
namespace bootserv
{
CronHandler::CronHandler(std::ostream& o) : Handler(o)
{
}
CronHandler::~CronHandler()
{
}
int
CronHandler::Exec(const Config& conf)
{
// this runs the cron tasks
// TODO: implement me
return 0;
}
int
CronHandler::ReportError(const char* err)
{
out << "error: " << err << std::endl;
return 1;
}
Handler_ptr
NewCronHandler(std::ostream& out)
{
return std::make_unique< CronHandler >(out);
}
} // namespace bootserv
} // namespace lokinet

@ -1,25 +0,0 @@
#ifndef BOOTSERV_LOKINET_CRON_HPP
#define BOOTSERV_LOKINET_CRON_HPP
#include "handler.hpp"
namespace lokinet
{
namespace bootserv
{
struct CronHandler final : public Handler
{
CronHandler(std::ostream& o);
~CronHandler();
int
Exec(const Config& conf) override;
int
ReportError(const char* err) override;
};
} // namespace bootserv
} // namespace lokinet
#endif

@ -1,60 +0,0 @@
#include "handler.hpp"
#include "lokinet-config.hpp"
#include <getopt.h>
#include <string_view>
#include <sstream>
static int
printhelp(const char* exe)
{
std::cout << "usage: " << exe << " [--cron] [--conf /path/to/alt/config.ini]"
<< std::endl;
return 1;
}
int
main(int argc, char* argv[])
{
bool RunCron = false;
const char* confFile = lokinet::bootserv::Config::DefaultPath;
lokinet::bootserv::Config config;
lokinet::bootserv::Handler_ptr handler;
option longopts[] = {{"cron", no_argument, 0, 'C'},
{"help", no_argument, 0, 'h'},
{"conf", required_argument, 0, 'c'},
{0, 0, 0, 0}};
int c = 0;
int index = 0;
while((c = getopt_long(argc, argv, "hCc:", longopts, &index)) != -1)
{
switch(c)
{
case 'h':
return printhelp(argv[0]);
case 'C':
RunCron = true;
break;
case 'c':
confFile = optarg;
break;
}
}
if(RunCron)
handler = lokinet::bootserv::NewCronHandler(std::cout);
else
handler = lokinet::bootserv::NewCGIHandler(std::cout);
if(!config.LoadFile(confFile))
{
std::stringstream ss;
ss << "failed to load " << confFile;
return handler->ReportError(ss.str().c_str());
}
else
return handler->Exec(config);
}

@ -82,7 +82,7 @@ def run_or_report(*args, myline):
log.write(e.output.encode())
global failure
failure = True
print_line(myline, "\033[31;1mError! See {} for details", log.name)
print_line(myline, "\033[31;1mError! See {} for details".format(log.name))
raise e

@ -43,6 +43,10 @@ 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"
else
cp -av daemon/lokinet daemon/lokinet-vpn "$base"
cp -av ../contrib/bootstrap/mainnet.signed "$base/bootstrap.signed"

@ -0,0 +1,65 @@
#!/bin/bash
#
# helper script for me for when i cross compile
# t. jeff
#
set -e
die() {
echo $@
exit 1
}
platform=${PLATFORM:-Linux}
root="$(readlink -e $(dirname $0)/../)"
cd $root
mkdir -p build-cross
cmake_opts="-DBUILD_STATIC_DEPS=ON \
-DSTATIC_LINK=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \
-DFORCE_OXENMQ_SUBMODULE=ON \
-DSUBMODULE_CHECK=OFF \
-DWITH_LTO=OFF \
-DWITH_BOOTSTRAP=OFF \
-DCMAKE_BUILD_TYPE=RelWithDeb"
targets=()
while [ "$#" -gt 0 ]; do
if [ "$1" = "--" ]; then
shift
cmake_opts=$@
break
fi
targets+=("$1")
shift
done
test ${#targets[@]} = 0 && die no targets provided
archs="${targets[@]}"
echo "all: $archs" > 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 \
$cmake_opts \
$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

@ -1,12 +0,0 @@
set(CMAKE_SYSTEM_NAME Linux)
set(TOOLCHAIN_PREFIX arm-linux-gnueabihf)
set(TOOLCHAIN_SUFFIX -8)
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${TOOLCHAIN_SUFFIX})
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${TOOLCHAIN_SUFFIX})

@ -1,7 +1,5 @@
set(CMAKE_SYSTEM_NAME Linux)
set(TOOLCHAIN_PREFIX aarch64-linux-gnu)
#set(TOOLCHAIN_SUFFIX)
set(CMAKE_SYSTEM_NAME ${CROSS_PLATFORM})
set(TOOLCHAIN_PREFIX ${CROSS_PREFIX})
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
@ -10,3 +8,4 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${TOOLCHAIN_SUFFIX})
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${TOOLCHAIN_SUFFIX})
set(ARCH_TRIPLET ${TOOLCHAIN_PREFIX})

@ -1,12 +0,0 @@
set(CMAKE_SYSTEM_NAME Linux)
set(TOOLCHAIN_PREFIX powerpc64le-linux-gnu)
set(TOOLCHAIN_SUFFIX -8)
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${TOOLCHAIN_SUFFIX})
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${TOOLCHAIN_SUFFIX})

@ -1,60 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!--
Created by Manifold
--><service_bundle type="manifest" name="lokinet">
<service name="site/lokinet" type="service" version="1">
<dependency name="network" grouping="require_all" restart_on="error" type="service">
<service_fmri value="svc:/milestone/network:default"/>
</dependency>
<dependency name="filesystem" grouping="require_all" restart_on="error" type="service">
<service_fmri value="svc:/system/filesystem/local"/>
</dependency>
<instance name="default" enabled="false">
<method_context>
<method_credential user="lokinet" group="lokinet"/>
</method_context>
<exec_method type="method" name="start" exec="/usr/bin/lokinet %{config_file}" timeout_seconds="60"/>
<exec_method type="method" name="stop" exec="/usr/bin/kill -INT &lt;&lt;&lt; `pgrep lokinet`" timeout_seconds="60"/>
<property_group name="startd" type="framework">
<propval name="duration" type="astring" value="child"/>
<propval name="ignore_error" type="astring" value="core,signal"/>
</property_group>
<property_group name="application" type="application">
<propval name="config_file" type="astring" value="/etc/loki/lokinet.ini"/>
</property_group>
</instance>
<stability value="Evolving"/>
<template>
<common_name>
<loctext xml:lang="C">
LokiNET: Anonymous Network layer thingydoo.
</loctext>
</common_name>
</template>
</service>
</service_bundle>

@ -1,18 +0,0 @@
#!/usr/sbin/dtrace -s
syscall:::entry
/pid == $target/
{
@calls[ustack(10), probefunc] = count();
}
profile:::tick-1sec
{
/** print */
printa(@calls);
/** clear */
clear(@calls);
trunc(@calls, 15);
}

@ -1,20 +0,0 @@
#!/bin/sh
. /etc/rc.subr
name=lokinet
rcvar=lokinet_enable
command="/usr/local/bin/${name}"
command_args="/usr/local/etc/${name}/daemon.ini > /dev/null 2>&1"
pidfile="/usr/local/etc/${name}/lokinet.pid"
required_files="/usr/local/etc/${name}/daemon.ini"
sig_reload="HUP"
start_precmd="${command} -g /usr/local/etc/${name}/daemon.ini"
load_rc_config $name
run_rc_command "$1"

@ -0,0 +1,10 @@
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)

@ -0,0 +1,13 @@
# 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

@ -0,0 +1,239 @@
#include <lokinet.h>
#include <signal.h>
#include <memory>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
bool _run{true};
using Lokinet_ptr = std::shared_ptr<lokinet_context>;
[[nodiscard]] auto
MakeLokinet(const std::vector<char>& bootstrap)
{
auto ctx = std::shared_ptr<lokinet_context>(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<ConnectJob*>(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<lokinet_context*>(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<Flow*>(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<Flow*>(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<Flow*>(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<char> 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;
}

@ -9,7 +9,7 @@
set -e
set +x
if ! [ -f LICENSE.txt ] || ! [ -d llarp ]; then
if ! [ -f LICENSE ] || ! [ -d llarp ]; then
echo "You need to run this as ./contrib/mac.sh from the top-level lokinet project directory"
fi

@ -1,79 +0,0 @@
#!/usr/bin/env python3
#
# requires python3-requests
#
import requests
import json
import os
import sys
from collections import defaultdict as Dict
from requests.exceptions import RequestException
def jsonrpc(method, **args):
return requests.post('http://127.0.0.1:1190/', data=json.dumps(
{'method': method, 'params': args, 'id': 'munin'}), headers={'content-type': 'application/json'}).json()
def exit_sessions_main():
if len(sys.argv) == 2 and sys.argv[1] == 'config':
print("graph_title lokinet exit sessions")
print("graph_vlabel sessions")
print("graph_category network")
print("graph_info This graph shows the number of exit sessions on a lokinet exit")
print("_exit_sessions.info Number of exit sessions")
print("_exit_sessions.label sessions")
else:
count = 0
try:
j = jsonrpc("llarp.admin.exit.list")
count = len(j['result'])
except RequestException:
pass
print("_exit_sessions.value {}".format(count))
def peers_main():
if len(sys.argv) == 2 and sys.argv[1] == 'config':
print("graph_title lokinet peers")
print("graph_vlabel peers")
print("graph_category network")
print("graph_info This graph shows the number of node to node sessions of this lokinet router")
print("_peers_outbound.info Number of outbound lokinet peers")
print("_peers_inbound.info Number of inbound lokinet peers")
print("_peers_outbound.label outbound peers")
print("_peers_inbound.label inbound peers")
print("_peers_clients.info Number of lokinet client peers")
print("_peers_clients.label lokinet client peers")
else:
inbound = Dict(int)
outbound = Dict(int)
clients = Dict(int)
try:
j = jsonrpc("llarp.admin.link.neighboors")
for peer in j['result']:
if peer["svcnode"]:
if peer["outbound"]:
outbound[peer['ident']] += 1
else:
inbound[peer['ident']] += 1
else:
clients[peer['ident']] += 1
except RequestException:
pass
print("_peers_outbound.value {}".format(len(outbound)))
print("_peers_inbound.value {}".format(len(inbound)))
print("_peers_clients.value {}".format(len(clients)))
if __name__ == '__main__':
exe = os.path.basename(sys.argv[0]).lower()
if exe == 'lokinet_peers':
peers_main()
elif exe == 'lokinet_exit':
exit_sessions_main()
else:
print(
'please symlink this as `lokinet_peers` or `lokinet_exit` in munin plugins dir')

@ -1,4 +0,0 @@
#!/usr/bin/env python3
from pylokinet.instance import main
main()

@ -1,111 +0,0 @@
#
# super freaking dead simple wicked awesome bencode library
#
from io import BytesIO
class BCodec:
encoding = 'utf-8'
def __init__(self, fd):
self._fd = fd
def _write_bytestring(self, bs):
self._fd.write('{}:'.format(len(bs)).encode('ascii'))
self._fd.write(bs)
def _write_list(self, l):
self._fd.write(b'l')
for item in l:
self.encode(item)
self._fd.write(b'e')
def _write_dict(self, d):
self._fd.write(b'd')
keys = list(d.keys())
keys.sort()
for k in keys:
if isinstance(k, str):
self._write_bytestring(k.encode(self.encoding))
elif isinstance(k, bytes):
self._write_bytestring(k)
else:
self._write_bytestring('{}'.format(k).encode(self.encoding))
self.encode(d[k])
self._fd.write(b'e')
def _write_int(self, i):
self._fd.write('i{}e'.format(i).encode(self.encoding))
def encode(self, obj):
if isinstance(obj, dict):
self._write_dict(obj)
elif isinstance(obj, list):
self._write_list(obj)
elif isinstance(obj, int):
self._write_int(obj)
elif isinstance(obj, str):
self._write_bytestring(obj.encode(self.encoding))
elif isinstance(obj, bytes):
self._write_bytestring(obj)
elif hasattr(obj, bencode):
obj.bencode(self._fd)
else:
raise ValueError("invalid object type")
def _readuntil(self, delim):
b = bytes()
while True:
ch = self._fd.read(1)
if ch == delim:
return b
b += ch
def _decode_list(self):
l = list()
while True:
b = self._fd.read(1)
if b == b'e':
return l
l.append(self._decode(b))
def _decode_dict(self):
d = dict()
while True:
ch = self._fd.read(1)
if ch == b'e':
return d
k = self._decode_bytestring(ch)
d[k] = self.decode()
def _decode_int(self):
return int(self._readuntil(b'e'), 10)
def _decode_bytestring(self, ch):
ch += self._readuntil(b':')
l = int(ch, base=10)
return self._fd.read(l)
def _decode(self, ch):
if ch == b'd':
return self._decode_dict()
elif ch == b'l':
return self._decode_list()
elif ch == b'i':
return self._decode_int()
elif ch in [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9']:
return self._decode_bytestring(ch)
else:
raise ValueError(ch)
def decode(self):
return self._decode(self._fd.read(1))
def bencode(obj):
buf = BytesIO()
b = BCodec(buf)
b.encode(obj)
return buf.getvalue()
def bdecode(bytestring):
buf = BytesIO(bytestring)
return BCodec(buf).decode()

@ -1,278 +0,0 @@
#!/usr/bin/env python3
#
# python wsgi application for managing many lokinet instances
#
__doc__ = """lokinet bootserv wsgi app
also handles webhooks for CI
run me with via gunicorn pylokinet.bootserv:app
"""
import os
from pylokinet import rc
import json
import random
import time
from datetime import datetime
from email.utils import parsedate, format_datetime
from dateutil.parser import parse as date_parse
import requests
root = './lokinet'
def _compare_dates(left, right):
"""
return true if left timestamp is bigger than right
"""
return date_parse(left) > date_parse(right)
class TokenHolder:
_dir = root
_token = None
def __init__(self, f="token"):
if not os.path.exists(self._dir):
os.mkdir(self._dir, 0o700)
f = os.path.join(self._dir, f)
if os.path.exists(f):
with open(f) as fd:
self._token = fd.read().replace("\n", "")
def verify(self, token):
"""
return true if token matches
"""
if self._token is None:
return False
return self._token == token
class BinHolder:
"""
serves a binary file in a dir
"""
_dir = os.path.join(root, 'bin')
def __init__(self, f):
if not os.path.exists(self._dir):
os.mkdir(self._dir, 0o700)
self._fpath = os.path.join(self._dir, f)
def put(self, r):
"""
put a new file into the place that is held
"""
with open(self._fpath, "wb") as f:
for chunk in r.iter_content(chunk_size=1024):
f.write(chunk)
def is_new(self, date):
"""
return true if last modified timestamp is fresher than current
"""
t = date_parse('{}'.format(date))
if not t:
return False
if os.path.exists(self._fpath):
st = os.stat(self._fpath)
return st.st_mtime < t.timestamp()
return True
def serve(self, last_modified, respond):
"""
serve file with caching
"""
t = parsedate(last_modified)
if t:
t = time.mktime(t)
if t is None:
t = 0
if not os.path.exists(self._fpath):
respond("404 Not Found", [])
return []
st = os.stat(self._fpath)
if st.st_mtime < t:
respond("304 Not Modified", [("Last-Modified", format_datetime(st.st_mtime)) ])
return []
with open(self._fpath, "rb") as f:
data = f.read()
respond("200 OK", [("Content-Type", "application/octect-stream"),
("Last-Modified", format_datetime(datetime.fromtimestamp(int(st.st_mtime)))),("Content-Length", "{}".format(st.st_size))])
return [data]
class RCHolder:
_dir = os.path.join(root, 'nodedb')
_rc_files = list()
def __init__(self):
if os.path.exists(self._dir):
for root, _, files in os.walk(self._dir):
for f in files:
self._add_rc(os.path.join(root, f))
else:
os.mkdir(self._dir, 0o700)
def prune(self):
"""
remove invalid entries
"""
delfiles = []
for p in self._rc_files:
with open(p, 'rb') as f:
if not rc.validate(f.read()):
delfiles.append(p)
for f in delfiles:
os.remove(f)
def validate_then_put(self, body):
if not rc.validate(body):
return False
k = rc.get_pubkey(body)
print(k)
if k is None:
return False
with open(os.path.join(self._dir, k), "wb") as f:
f.write(body)
return True
def _add_rc(self, fpath):
self._rc_files.append(fpath)
def serve_random(self):
with open(random.choice(self._rc_files), 'rb') as f:
return f.read()
def empty(self):
return len(self._rc_files) == 0
def handle_rc_upload(body, respond):
holder = RCHolder()
if holder.validate_then_put(body):
respond("200 OK", [("Content-Type", "text/plain")])
return ["rc accepted".encode('ascii')]
else:
respond("400 Bad Request", [("Content-Type", "text/plain")])
return ["bad rc".encode('ascii')]
def serve_random_rc():
holder = RCHolder()
if holder.empty():
return None
else:
return holder.serve_random()
def response(status, msg, respond):
respond(status, [("Content-Type", "text/plain"), ("Content-Length", "{}".format(len(msg)))])
return [msg.encode("utf-8")]
def handle_serve_lokinet(modified_since, respond):
l = BinHolder('lokinet.zip')
return l.serve(modified_since, respond)
def fetch_lokinet(j, ref="staging", name="build:linux"):
holder = BinHolder("lokinet.zip")
if 'builds' not in j:
return False
selected = None
attrs = dict()
if 'object_attributes' in j:
attrs = j['object_attributes']
if 'ref' not in attrs or attrs["ref"] != ref:
return True
for build in j['builds']:
if 'name' not in build or build['name'] != name:
continue
if 'status' not in build or build['status'] != 'success':
continue
if 'finished_at' not in build or build['finished_at'] is None:
continue
if holder.is_new(build['finished_at']):
if selected is None or _compare_dates(build["finished_at"], selected["finished_at"]):
selected = build
if selected and 'id' in selected:
url = 'https://gitlab.com/lokiproject/loki-network/-/jobs/{}/artifacts/download'.format(selected['id'])
r = requests.get(url)
if r.status_code == 200:
holder.put(r)
return True
#if 'artifacts_file' not in selected:
# return False
#f = selected["artifacts_file"]
#return True
def handle_webhook(j, token, event, respond):
"""
handle CI webhook
"""
t = TokenHolder()
if not t.verify(token):
respond("403 Forbidden", [])
return []
event = event.lower()
if event == 'pipeline hook':
if fetch_lokinet(j):
respond("200 OK", [])
return []
else:
respond("500 Internal Server Error", [])
return []
else:
respond("404 Not Found", [])
return []
def app(environ, start_response):
request_body_size = int(environ.get("CONTENT_LENGTH", 0))
method = environ.get("REQUEST_METHOD")
if method.upper() == "PUT" and request_body_size > 0:
rcbody = environ.get("wsgi.input").read(request_body_size)
return handle_rc_upload(rcbody, start_response)
elif method.upper() == "POST":
if environ.get("PATH_INFO") == "/":
j = json.loads(environ.get("wsgi.input").read(request_body_size))
token = environ.get("HTTP_X_GITLAB_TOKEN")
return handle_webhook(j, token, environ.get("HTTP_X_GITLAB_EVENT"), start_response)
else:
return response("404 Not Found", 'bad url', start_response)
elif method.upper() == "GET":
if environ.get("PATH_INFO") == "/bootstrap.signed":
resp = serve_random_rc()
if resp is not None:
start_response('200 OK', [("Content-Type", "application/octet-stream")])
return [resp]
else:
return response('404 Not Found', 'no RCs', start_response)
elif environ.get("PATH_INFO") == "/ping":
return response('200 OK', 'pong', start_response)
elif environ.get("PATH_INFO") == "/lokinet.zip":
return handle_serve_lokinet(environ.get("HTTP_IF_MODIFIED_SINCE"),start_response)
elif environ.get("PATH_INFO") == "/":
return response("200 OK", "lokinet bootserv", start_response)
else:
return response('404 Not Found', 'Not found', start_response)
else:
return response('405 Method Not Allowed', 'method not allowed', start_response)
def main():
"""
run as cron job
"""
h = RCHolder()
h.prune()
if __name__ == '__main__':
main()

@ -1,224 +0,0 @@
#!/usr/bin/env python3
#
# lokinet runtime wrapper
#
from ctypes import *
import configparser
import signal
import time
import threading
import os
import sys
import requests
from pylokinet import rc
lib_file = os.path.join(os.path.realpath('.'), 'liblokinet-shared.so')
def log(msg):
sys.stderr.write("lokinet: {}\n".format(msg))
sys.stderr.flush()
class LokiNET(threading.Thread):
lib = None
ctx = 0
failed = False
up = False
asRouter = True
def configure(self, lib, conf, ip=None, port=None, ifname=None, seedfile=None, lokid_host=None, lokid_port=None):
log("configure lib={} conf={}".format(lib, conf))
if not os.path.exists(os.path.dirname(conf)):
os.mkdir(os.path.dirname(conf))
try:
self.lib = CDLL(lib)
except OSError as ex:
log("failed to load library: {}".format(ex))
return False
if self.lib.llarp_ensure_config(conf.encode('utf-8'), os.path.dirname(conf).encode('utf-8'), True, self.asRouter):
config = configparser.ConfigParser()
config.read(conf)
log('overwrite ip="{}" port="{}" ifname="{}" seedfile="{}" lokid=("{}", "{}")'.format(
ip, port, ifname, seedfile, lokid_host, lokid_port))
if seedfile and lokid_host and lokid_port:
if not os.path.exists(seedfile):
log('cannot access service node seed at "{}"'.format(seedfile))
return False
config['lokid'] = {
'service-node-seed': seedfile,
'enabled': "true",
'jsonrpc': "{}:{}".format(lokid_host, lokid_port)
}
if ip:
config['router']['public-address'] = '{}'.format(ip)
if port:
config['router']['public-port'] = '{}'.format(port)
if ifname and port:
config['bind'] = {
ifname: '{}'.format(port)
}
with open(conf, "w") as f:
config.write(f)
self.ctx = self.lib.llarp_main_init(conf.encode('utf-8'))
else:
return False
return self.lib.llarp_main_setup(self.ctx, False) == 0
def inform_fail(self):
"""
inform lokinet crashed
"""
self.failed = True
self._inform()
def inform_up(self):
self.up = True
self._inform()
def _inform(self):
"""
inform waiter
"""
def wait_for_up(self, timeout):
"""
wait for lokinet to go up for :timeout: seconds
:return True if we are up and running otherwise False:
"""
# return self._up.wait(timeout)
def signal(self, sig):
if self.ctx and self.lib:
self.lib.llarp_main_signal(self.ctx, int(sig))
def run(self):
# self._up.acquire()
self.up = True
code = self.lib.llarp_main_run(self.ctx)
log("llarp_main_run exited with status {}".format(code))
if code:
self.inform_fail()
self.up = False
# self._up.release()
def close(self):
if self.lib and self.ctx:
self.lib.llarp_main_free(self.ctx)
def getconf(name, fallback=None):
return name in os.environ and os.environ[name] or fallback
def run_main(args):
seedfile = getconf("LOKI_SEED_FILE")
if seedfile is None:
print("LOKI_SEED_FILE was not set")
return
lokid_host = getconf("LOKI_RPC_HOST", "127.0.0.1")
lokid_port = getconf("LOKI_RPC_PORT", "22023")
root = getconf("LOKINET_ROOT")
if root is None:
print("LOKINET_ROOT was not set")
return
rc_callback = getconf("LOKINET_SUBMIT_URL")
if rc_callback is None:
print("LOKINET_SUBMIT_URL was not set")
return
bootstrap = getconf("LOKINET_BOOTSTRAP_URL")
if bootstrap is None:
print("LOKINET_BOOTSTRAP_URL was not set")
lib = getconf("LOKINET_LIB", lib_file)
if not os.path.exists(lib):
lib = "liblokinet-shared.so"
timeout = int(getconf("LOKINET_TIMEOUT", "5"))
ping_interval = int(getconf("LOKINET_PING_INTERVAL", "60"))
ping_callback = getconf("LOKINET_PING_URL")
ip = getconf("LOKINET_IP")
port = getconf("LOKINET_PORT")
ifname = getconf("LOKINET_IFNAME")
if ping_callback is None:
print("LOKINET_PING_URL was not set")
return
conf = os.path.join(root, "daemon.ini")
log("going up")
loki = LokiNET()
log("bootstrapping...")
try:
r = requests.get(bootstrap)
if r.status_code == 404:
log("bootstrap gave no RCs, we are probably the seed node")
elif r.status_code != 200:
raise Exception("http {}".format(r.status_code))
else:
data = r.content
if rc.validate(data):
log("valid RC obtained")
with open(os.path.join(root, "bootstrap.signed"), "wb") as f:
f.write(data)
else:
raise Exception("invalid RC")
except Exception as ex:
log("failed to bootstrap: {}".format(ex))
loki.close()
return
if loki.configure(lib, conf, ip, port, ifname, seedfile, lokid_host, lokid_port):
log("configured")
loki.start()
try:
log("waiting for spawn")
while timeout > 0:
time.sleep(1)
if loki.failed:
log("failed")
break
log("waiting {}".format(timeout))
timeout -= 1
if loki.up:
log("submitting rc")
try:
with open(os.path.join(root, 'self.signed'), 'rb') as f:
r = requests.put(rc_callback, data=f.read(), headers={
"content-type": "application/octect-stream"})
log('submit rc reply: HTTP {}'.format(r.status_code))
except Exception as ex:
log("failed to submit rc: {}".format(ex))
loki.signal(signal.SIGINT)
time.sleep(2)
else:
while loki.up:
time.sleep(ping_interval)
try:
r = requests.get(ping_callback)
log("ping reply: HTTP {}".format(r.status_code))
except Exception as ex:
log("failed to submit ping: {}".format(ex))
else:
log("failed to go up")
loki.signal(signal.SIGINT)
except KeyboardInterrupt:
loki.signal(signal.SIGINT)
time.sleep(2)
finally:
loki.close()
else:
loki.close()
def main():
run_main(sys.argv[1:])
if __name__ == "__main__":
main()

@ -1,31 +0,0 @@
from pylokinet import bencode
import pysodium
import binascii
import time
def _expired(ts, lifetime=84600000):
"""
return True if a timestamp is considered expired
lifetime is default 23.5 hours
"""
return (int(time.time()) * 1000) - ts >= lifetime
def validate(data):
rc = bencode.bdecode(data)
if b'z' not in rc or b'k' not in rc:
return False
sig = rc[b'z']
rc[b'z'] = b'\x00' * 64
buf = bencode.bencode(rc)
try:
k = rc[b'k']
pysodium.crypto_sign_verify_detached(sig, buf, k)
except:
return False
else:
return not _expired(rc[b't'])
def get_pubkey(data):
rc = bencode.bdecode(data)
if b'k' in rc:
return binascii.hexlify(rc[b'k']).decode('ascii')

@ -1,27 +0,0 @@
# pylokinet
lokinet with python 3
# python3 setup.py install
## bootserv
bootserv is a bootstrap server for accepting and serving RCs
$ gunicorn -b 0.0.0.0:8000 pylokinet.bootserv:app
## pylokinet instance
obtain `liblokinet-shared.so` from a lokinet build
run (root):
# export LOKINET_ROOT=/tmp/lokinet-instance/
# export LOKINET_LIB=/path/to/liblokinet-shared.so
# export LOKINET_BOOTSTRAP_URL=http://bootserv.ip.address.here:8000/bootstrap.signed
# export LOKINET_PING_URL=http://bootserv.ip.address.here:8000/ping
# export LOKINET_SUBMIT_URL=http://bootserv.ip.address.here:8000/
# export LOKINET_IP=public.ip.goes.here
# export LOKINET_PORT=1090
# export LOKINET_IFNAME=eth0
# python3 -m pylokinet

@ -1,14 +0,0 @@
from setuptools import setup, find_packages
setup(
name="pylokinet",
version="0.0.1",
license="ZLIB",
author="jeff",
author_email="jeff@i2p.rocks",
description="lokinet python bindings",
url="https://github.com/loki-project/loki-network",
install_requires=["pysodium", "requests", "python-dateutil"],
packages=find_packages())

@ -1,2 +0,0 @@
__pycache__
*.private

@ -1,112 +0,0 @@
#
# super freaking dead simple wicked awesome bencode library
#
from io import BytesIO
class BCodec:
encoding = 'utf-8'
def __init__(self, fd):
self._fd = fd
def _write_bytestring(self, bs):
self._fd.write('{}:'.format(len(bs)).encode('ascii'))
self._fd.write(bs)
def _write_list(self, l):
self._fd.write(b'l')
for item in l:
self.encode(item)
self._fd.write(b'e')
def _write_dict(self, d):
self._fd.write(b'd')
keys = list(d.keys())
keys.sort()
for k in keys:
if isinstance(k, str):
self._write_bytestring(k.encode(self.encoding))
elif isinstance(k, bytes):
self._write_bytestring(k)
else:
self._write_bytestring('{}'.format(k).encode(self.encoding))
self.encode(d[k])
self._fd.write(b'e')
def _write_int(self, i):
self._fd.write('i{}e'.format(i).encode(self.encoding))
def encode(self, obj):
if isinstance(obj, dict):
self._write_dict(obj)
elif isinstance(obj, list):
self._write_list(obj)
elif isinstance(obj, int):
self._write_int(obj)
elif isinstance(obj, str):
self._write_bytestring(obj.encode(self.encoding))
elif isinstance(obj, bytes):
self._write_bytestring(obj)
elif hasattr(obj, bencode):
obj.bencode(self._fd)
else:
raise ValueError("invalid object type")
def _readuntil(self, delim):
b = bytes()
while True:
ch = self._fd.read(1)
if ch == delim:
return b
b += ch
def _decode_list(self):
l = list()
while True:
b = self._fd.read(1)
if b == b'e':
return l
l.append(self._decode(b))
def _decode_dict(self):
d = dict()
while True:
ch = self._fd.read(1)
if ch == b'e':
return d
k = self._decode_bytestring(ch)
d[k] = self.decode()
def _decode_int(self):
return int(self._readuntil(b'e'), 10)
def _decode_bytestring(self, ch):
ch += self._readuntil(b':')
l = int(ch, base=10)
return self._fd.read(l)
def _decode(self, ch):
if ch == b'd':
return self._decode_dict()
elif ch == b'l':
return self._decode_list()
elif ch == b'i':
return self._decode_int()
elif ch in [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9']:
return self._decode_bytestring(ch)
else:
raise ValueError(ch)
def decode(self):
return self._decode(self._fd.read(1))
def bencode(obj):
buf = BytesIO()
b = BCodec(buf)
b.encode(obj)
return buf.bytes()
def bdecode(bytestring):
buf = BytesIO()
buf.write(bytestring)
return BCodec(buf).decode()

@ -1,138 +0,0 @@
#!/usr/bin/env python3
import bencode
import sys
import libnacl
import struct
from io import BytesIO
import time
from multiprocessing import Process, Array, Value
def print_help():
print('usage: {} keyfile.private prefix numthreads'.format(sys.argv[0]))
return 1
_zalpha = ['y', 'b', 'n', 'd', 'r', 'f', 'g', '8',
'e', 'j', 'k', 'm', 'c', 'p', 'q', 'x',
'o', 't', '1', 'u', 'w', 'i', 's', 'z',
'a', '3', '4', '5', 'h', '7', '6', '9']
def zb32_encode(buf):
s = str()
bits = 0
l = len(buf)
idx = 0
tmp = buf[idx]
while bits > 0 or idx < l:
if bits < 5:
if idx < l:
tmp <<= 8
tmp |= buf[idx] & 0xff
idx += 1
bits += 8
else:
tmp <<= 5 - bits
bits = 5
bits -= 5
s += _zalpha[(tmp >> bits) & 0x1f]
return s
def _gen_si(keys):
e = keys[b'e'][32:]
s = keys[b's'][32:]
v = keys[b'v']
return {'e': e, 's': s, 'v': v}
class AddrGen:
def __init__(self, threads, keys, prefix):
self._inc = threads
self._keys = keys
self._c = Value('i')
self.sync = Array('i', 3)
self._procs = []
self.prefix = prefix
def runit(self):
for ch in self.prefix:
if ch not in _zalpha:
print("invalid prefix, {} not a valid character".format(ch))
return None, None
print("find ^{}.loki".format(self.prefix))
i = self._inc
while i > 0:
p = Process(target=self._gen_addr_tick, args=(self.prefix, abs(
libnacl.randombytes_random()), abs(libnacl.randombytes_random()), _gen_si(self._keys)))
p.start()
self._procs.append(p)
i -= 1
return self._runner()
def _gen_addr_tick(self, prefix, lo, hi, si):
print(prefix)
fd = BytesIO()
addr = ''
enc = bencode.BCodec(fd)
while self.sync[2] == 0:
si['x'] = struct.pack('>QQ', lo, hi)
fd.seek(0, 0)
enc.encode(si)
pub = bytes(fd.getbuffer())
addr = zb32_encode(libnacl.crypto_generichash(pub))
if addr.startswith(prefix):
self.sync[2] = 1
self.sync[0] = hi
self.sync[1] = lo
return
hi += self._inc
if hi == 0:
lo += 1
self._c.value += 1
def _print_stats(self):
print('{} H/s'.format(self._c.value))
self._c.value = 0
def _joinall(self):
for p in self._procs:
p.join()
def _runner(self):
while self.sync[2] == 0:
time.sleep(1)
self._print_stats()
self._joinall()
fd = BytesIO()
enc = bencode.BCodec(fd)
hi = self.sync[0]
lo = self.sync[1]
si = _gen_si(self._keys)
si['x'] = struct.pack('>QQ', lo, hi)
enc.encode(si)
pub = bytes(fd.getbuffer())
addr = zb32_encode(libnacl.crypto_generichash(pub))
return si['x'], addr
def main(args):
if len(args) != 3:
return print_help()
keys = None
with open(args[0], 'rb') as fd:
dec = bencode.BCodec(fd)
keys = dec.decode()
runner = AddrGen(int(args[2]), keys, args[1])
keys[b'x'], addr = runner.runit()
if addr:
print("found {}.loki".format(addr))
with open(args[0], 'wb') as fd:
enc = bencode.BCodec(fd)
enc.encode(keys)
if __name__ == '__main__':
main(sys.argv[1:])

@ -1,10 +0,0 @@
# lokinet vanity address generator
installing deps:
sudo apt install libsodium-dev
pip3 install --user -r requirements.txt
to generate a nonce with a prefix `^7oki` using 8 cpu threads:
python3 lokinet-vanity.py keyfile.private 7oki 8

@ -1,141 +0,0 @@
#!/usr/bin/env python3
import configparser
import sys
import os
from xml.etree import ElementTree as etree
def getSetting(s, name, fallback): return name in s and s[name] or fallback
shadowRoot = getSetting(os.environ, "SHADOW_ROOT",
os.path.join(os.environ['HOME'], '.shadow'))
libpath = 'libshadow-plugin-lokinet.so'
def nodeconf(conf, baseDir, name, ifname=None, port=None):
conf['netdb'] = {'dir': 'tmp-nodes'}
conf['router'] = {}
conf['router']['contact-file'] = os.path.join(
baseDir, '{}.signed'.format(name))
conf['router']['ident-privkey'] = os.path.join(
baseDir, '{}-ident.key'.format(name))
conf['router']['transport-privkey'] = os.path.join(
baseDir, '{}-transport.key'.format(name))
if ifname and port:
conf['bind'] = {ifname: port}
conf['connect'] = {}
def addPeer(conf, baseDir, peer):
conf['connect'][peer] = os.path.join(baseDir, '{}.signed'.format(peer))
def createNode(pluginName, root, peer, life=600):
node = etree.SubElement(root, 'node')
node.attrib['id'] = peer['name']
node.attrib['interfacebuffer'] = '{}'.format(1024 * 1024 * 100)
app = etree.SubElement(node, 'process')
app.attrib['plugin'] = pluginName
app.attrib['time'] = '{}'.format(life)
app.attrib['arguments'] = peer['configfile']
def makeBase(settings, name, id):
return {
'id': id,
'name': name,
'contact-file': os.path.join(getSetting(settings, 'baseDir', 'tmp'), '{}.signed'.format(name)),
'configfile': os.path.join(getSetting(settings, 'baseDir', 'tmp'), '{}.ini'.format(name)),
'config': configparser.ConfigParser()
}
def makeClient(settings, name, id):
peer = makeBase(settings, name, id)
basedir = getSetting(settings, 'baseDir', 'tmp')
nodeconf(peer['config'], basedir, name)
peer['config']['network'] = {
'type': 'null',
'tag': 'test',
'prefetch-tag': 'test'
}
return peer
def makeSVCNode(settings, name, id, port):
peer = makeBase(settings, name, id)
nodeconf(peer['config'], getSetting(
settings, 'baseDir', 'tmp'), name, 'eth0', port)
peer['config']['network'] = {
'type': 'null'
}
return peer
def genconf(settings, outf):
root = etree.Element('shadow')
root.attrib["environment"] = 'LLARP_SHADOW=1'
topology = etree.SubElement(root, 'topology')
topology.attrib['path'] = getSetting(settings, 'topology', os.path.join(
shadowRoot, 'share', 'topology.graphml.xml'))
pluginName = getSetting(settings, 'name', 'lokinet-shared')
kill = etree.SubElement(root, 'kill')
kill.attrib['time'] = getSetting(settings, 'runFor', '600')
baseDir = getSetting(settings, 'baseDir',
os.path.join('/tmp', 'lokinet-shadow'))
if not os.path.exists(baseDir):
os.mkdir(baseDir)
plugin = etree.SubElement(root, "plugin")
plugin.attrib['id'] = pluginName
plugin.attrib['path'] = libpath
basePort = getSetting(settings, 'svc-base-port', 19000)
svcNodeCount = getSetting(settings, 'service-nodes', 80)
peers = list()
for nodeid in range(svcNodeCount):
peers.append(makeSVCNode(
settings, 'svc-node-{}'.format(nodeid), str(nodeid), basePort + 1))
basePort += 1
# make all service nodes know each other
for peer in peers:
for nodeid in range(svcNodeCount):
if str(nodeid) != peer['id']:
addPeer(peer['config'], baseDir, 'svc-node-{}'.format(nodeid))
# add client nodes
for nodeid in range(getSetting(settings, 'client-nodes', 200)):
peer = makeClient(
settings, 'client-node-{}'.format(nodeid), str(nodeid))
peers.append(peer)
for p in range(getSetting(settings, 'client-connect-to', 10)):
addPeer(peer['config'], baseDir,
'svc-node-{}'.format((p + nodeid) % svcNodeCount))
# generate xml and settings files
for peer in peers:
createNode(pluginName, root, peer)
with open(peer['configfile'], 'w') as f:
peer['config'].write(f)
# render
outf.write(etree.tostring(root).decode('utf-8'))
if __name__ == '__main__':
settings = {
'baseDir': os.path.join("/tmp", "lokinet-shadow"),
'topology': os.path.join(shadowRoot, 'share', 'topology.graphml.xml'),
'runFor': '{}'.format(60 * 10 * 10)
}
with open(sys.argv[1], 'w') as f:
genconf(settings, f)

@ -1,157 +0,0 @@
#!/usr/bin/env python
#
# this script generate supervisord configs for running a test network on loopback
#
from argparse import ArgumentParser as AP
from configparser import ConfigParser as CP
import os
def svcNodeName(id): return 'svc-node-%03d' % id
def clientNodeName(id): return 'client-node-%03d' % id
def main():
ap = AP()
ap.add_argument('--valgrind', type=bool, default=False)
ap.add_argument('--dir', type=str, default='testnet_tmp')
ap.add_argument('--svc', type=int, default=20,
help='number of service nodes')
ap.add_argument('--baseport', type=int, default=19000)
ap.add_argument('--clients', type=int, default=200,
help='number of client nodes')
ap.add_argument('--bin', type=str, required=True)
ap.add_argument('--out', type=str, required=True)
ap.add_argument('--connect', type=int, default=10)
ap.add_argument('--ip', type=str, default=None)
ap.add_argument('--ifname', type=str, default='lo')
ap.add_argument('--netid', type=str, default=None)
ap.add_argument('--loglevel', type=str, default='debug')
args = ap.parse_args()
if args.valgrind:
exe = 'valgrind {}'.format(args.bin)
else:
exe = '{} -v'.format(args.bin)
basedir = os.path.abspath(args.dir)
for nodeid in range(args.svc):
config = CP()
config['router'] = {
'data-dir': '.',
'net-threads': '1',
'worker-threads': '4',
'nickname': svcNodeName(nodeid),
'min-connections': "{}".format(args.connect)
}
if args.netid:
config['router']['netid'] = args.netid
if args.ip:
config['router']['public-ip'] = args.ip
config['router']['public-port'] = str(args.baseport + nodeid)
config['bind'] = {
args.ifname: str(args.baseport + nodeid)
}
config["logging"] = {
"level": args.loglevel
}
config['netdb'] = {
'dir': 'netdb'
}
config['network'] = {
'type' : 'null',
'save-profiles': 'false'
}
config['api'] = {
'enabled': 'false'
}
config['lokid'] = {
'enabled': 'false',
}
config["logging"] = {
"level": args.loglevel
}
d = os.path.join(args.dir, svcNodeName(nodeid))
if not os.path.exists(d):
os.mkdir(d)
fp = os.path.join(d, 'daemon.ini')
with open(fp, 'w') as f:
config.write(f)
for n in [0]:
if nodeid is not 0:
f.write("[bootstrap]\nadd-node={}\n".format(os.path.join(basedir,svcNodeName(n), 'self.signed')))
else:
f.write("[bootstrap]\nseed-node=true\n")
for nodeid in range(args.clients):
config = CP()
config['router'] = {
'data-dir': '.',
'net-threads': '1',
'worker-threads': '2',
'nickname': clientNodeName(nodeid)
}
if args.netid:
config['router']['netid'] = args.netid
config["logging"] = {
"level": args.loglevel
}
config['netdb'] = {
'dir': 'netdb'
}
config['api'] = {
'enabled': 'false'
}
config['network'] = {
'type' : 'null'
}
d = os.path.join(args.dir, clientNodeName(nodeid))
if not os.path.exists(d):
os.mkdir(d)
fp = os.path.join(d, 'client.ini')
with open(fp, 'w') as f:
config.write(f)
for n in [0]:
otherID = (n + nodeid) % args.svc
f.write("[bootstrap]\nadd-node={}\n".format(os.path.join(basedir,svcNodeName(otherID), 'self.signed')))
with open(args.out, 'w') as f:
basedir = os.path.join(args.dir, 'svc-node-%(process_num)03d')
f.write('''[program:svc-node]
directory = {}
command = {} -r {}/daemon.ini
autorestart=true
redirect_stderr=true
#stdout_logfile=/dev/fd/1
stdout_logfile={}/svc-node-%(process_num)03d-log.txt
stdout_logfile_maxbytes=0
process_name = svc-node-%(process_num)03d
numprocs = {}
'''.format(basedir, exe, basedir, args.dir, args.svc))
basedir = os.path.join(args.dir, 'client-node-%(process_num)03d')
f.write('''[program:Client-node]
directory = {}
command = bash -c "sleep 5 && {} {}/client.ini"
autorestart=true
redirect_stderr=true
#stdout_logfile=/dev/fd/1
stdout_logfile={}/client-node-%(process_num)03d-log.txt
stdout_logfile_maxbytes=0
process_name = client-node-%(process_num)03d
numprocs = {}
'''.format(basedir, exe, basedir, args.dir, args.clients))
f.write('[supervisord]\ndirectory=.\n')
if __name__ == '__main__':
main()

@ -1,23 +0,0 @@
loopback testnet scripts
requirements:
* bash
* python3
* supervisord
setup:
make a testnet compatable lokinet build:
$ cmake -DWITH_TESTNET=ON -B build-testnet -S .
$ make -C build-testnet lokinet
usage:
from root of repo run:
$ ./contrib/testnet/testnet.sh build-testnet/daemon/lokinet 20 200
this will spin up 20 service nodes and 200 clients

@ -1,15 +0,0 @@
#!/usr/bin/env bash
for arg in "$1" "$2" "$3" ; do
test x = "x$arg" && echo "usage: $0 path/to/lokinet num_svc num_clients" && exit 1;
done
script_root=$(dirname $(readlink -e $0))
testnet_dir=/tmp/lokinet-testnet
mkdir -p $testnet_dir
set -x
$script_root/genconf.py --bin $1 --netid=testnet --out=$testnet_dir/testnet.ini --svc $2 --dir=$testnet_dir --clients $3 || exit 1
supervisord -n -c $testnet_dir/testnet.ini

@ -17,12 +17,13 @@ cmake \
-DBUILD_PACKAGE=ON \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_LIBLOKINET=ON \
-DBUILD_LIBLOKINET=OFF \
-DWITH_TESTS=OFF \
-DNATIVE_BUILD=OFF \
-DSTATIC_LINK=ON \
-DWITH_SYSTEMD=OFF \
-DFORCE_OXENMQ_SUBMODULE=ON \
-DFORCE_OXENC_SUBMODULE=ON \
-DSUBMODULE_CHECK=OFF \
-DWITH_LTO=OFF \
-DCMAKE_BUILD_TYPE=Release \

@ -1,8 +1,3 @@
set(DEFAULT_WITH_BOOTSTRAP ON)
if(APPLE)
set(DEFAULT_WITH_BOOTSTRAP OFF)
endif()
option(WITH_BOOTSTRAP "build lokinet-bootstrap tool" ${DEFAULT_WITH_BOOTSTRAP})
add_executable(lokinet-vpn lokinet-vpn.cpp)
if(APPLE)
@ -11,11 +6,10 @@ if(APPLE)
else()
add_executable(lokinet lokinet.cpp)
enable_lto(lokinet lokinet-vpn)
if(WITH_BOOTSTRAP)
add_executable(lokinet-bootstrap lokinet-bootstrap.cpp)
enable_lto(lokinet-bootstrap)
endif()
endif()
if(WITH_BOOTSTRAP)
add_executable(lokinet-bootstrap lokinet-bootstrap.cpp)
enable_lto(lokinet-bootstrap)
endif()
@ -55,7 +49,7 @@ endif()
foreach(exe ${exetargets})
if(WIN32 AND NOT MSVC_VERSION)
target_sources(${exe} PRIVATE ../llarp/win32/version.rc)
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)
target_link_libraries(${exe} PRIVATE ws2_32 iphlpapi)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")

@ -401,19 +401,17 @@ lokinet_main(int argc, char* argv[])
"and IP based onion routing network");
// clang-format off
options.add_options()
("v,verbose", "Verbose", cxxopts::value<bool>())
#ifdef _WIN32
("install", "install win32 daemon to SCM", cxxopts::value<bool>())
("remove", "remove win32 daemon from SCM", cxxopts::value<bool>())
#endif
("h,help", "help", cxxopts::value<bool>())("version", "version", cxxopts::value<bool>())
("g,generate", "generate client config", cxxopts::value<bool>())
("r,router", "run as router instead of client", cxxopts::value<bool>())
("f,force", "overwrite", cxxopts::value<bool>())
("h,help", "print this help message", cxxopts::value<bool>())
("version", "print version string", cxxopts::value<bool>())
("g,generate", "generate default configuration and exit", cxxopts::value<bool>())
("r,router", "run in routing mode instead of client only mode", cxxopts::value<bool>())
("f,force", "force writing config even if it already exists", cxxopts::value<bool>())
("c,colour", "colour output", cxxopts::value<bool>()->default_value("true"))
("b,background", "background mode (start, but do not connect to the network)",
cxxopts::value<bool>())
("config", "path to configuration file", cxxopts::value<std::string>())
("config", "path to lokinet.ini configuration file", cxxopts::value<std::string>())
;
// clang-format on
@ -426,12 +424,6 @@ lokinet_main(int argc, char* argv[])
{
auto result = options.parse(argc, argv);
if (result.count("verbose") > 0)
{
SetLogLevel(llarp::eLogDebug);
llarp::LogDebug("debug logging activated");
}
if (!result["colour"].as<bool>())
{
llarp::LogContext::Instance().logStream =
@ -467,11 +459,6 @@ lokinet_main(int argc, char* argv[])
genconfigOnly = true;
}
if (result.count("background") > 0)
{
opts.background = true;
}
if (result.count("router") > 0)
{
opts.isSNode = true;

@ -3,37 +3,82 @@ if (NOT DOXYGEN)
message(STATUS "Documentation generation disabled (doxygen not found)")
return()
endif()
find_program(SPHINX_BUILD sphinx-build)
if (NOT SPHINX_BUILD)
message(STATUS "Documentation generation disabled (sphinx-build not found)")
find_program(MKDOCS mkdocs)
if (NOT MKDOCS)
message(STATUS "Documentation generation disabled (mkdocs not found)")
return()
endif()
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 html/index.html
COMMAND ${SPHINX_BUILD} -j auto
-Dbreathe_projects.lokinet=${CMAKE_CURRENT_BINARY_DIR}/doxyxml
-Dversion=${lokinet_VERSION} -Drelease=${lokinet_VERSION}
-Aversion=${lokinet_VERSION} -Aversions=${lokinet_VERSION_MAJOR},${lokinet_VERSION_MINOR},${lokinet_VERSION_PATCH}
-b html
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/html
OUTPUT gen
COMMAND ${DOXYBOOK2} --input ${CMAKE_CURRENT_BINARY_DIR}/doxyxml --output ${CMAKE_CURRENT_BINARY_DIR}/gen --config config.json
DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/index.rst
${CMAKE_CURRENT_BINARY_DIR}/conf.py
${CMAKE_CURRENT_BINARY_DIR}/doxyxml/index.xml
${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_target(doc DEPENDS html/index.html)
configure_file(conf.py.in conf.py @ONLY)
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.rst index.rst COPYONLY)
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)

@ -1,5 +0,0 @@
Protocol Specifications Directory
All documents in this directory are licened CC0 and placed into the public domain.
Please note that the reference implementation LokiNET is licensed under ZLIB license

@ -1,263 +0,0 @@
LLARP Traffic Routing Protocol (LTRP)
LRTP is a protocol that instructs how to route hidden service traffic on LLARP
based networks.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
Overview:
LRTP is a message oriented data delivery and receival protocol for hidden
service traffic. All structures are BitTorrent Encoded dictionaries sent
over TCP.
all structures are bencoded when sent over the networks.
In this document they are provided in JSON for ease of display.
message format:
<2 bytes length (N)>
<N bytes of data>
Nouns (data structures):
Path: information about a path that we have built
{
H: [router_id_32_bytes, router_id_32_bytes, router_id_32_bytes, router_id_32_bytes],
R: "<16 bytes local rxid>",
T: "<16 bytes local txid>"
}
Introduction: a hidden service introduction
{
E: expiration_ms_since_epoch_uint64,
L: advertised_latency_ms_uint64,
P: "<16 bytes pathid>",
R: "<32 bytes RouterID>"
}
ServiceInfo: public key info for hidden service address
{
A: "<32 bytes .loki address>",
E: "<32 bytes public encryption key>",
S: "<32 bytes public signing key>"
}
IntroSet: information about an introduction set from the network
{
E: expires_at_timestamp_ms_since_epoch_uint64,
I: [Intro0, Intro1, ... IntroN],
S: ServiceInfo
}
Converstation: information about a loki network converstation
{
L: "<32 bytes loki address provided if a loki address>",
S: "<32 bytes snode address provided if a snode address>",
T: "<16 bytes convo tag>"
}
SessionInfo: information about our current session
{
I: [inbound,convos,here],
O: [outbound,covos,here],
P: [Path0, Path1, .... PathN],
S: Current IntroSet,
}
Verbs (methods):
session requset (C->S)
the first message sent by the client
{
A: "session",
B: "<8 bytes random>",
T: milliseconds_since_epoch_client_now_uint64,
Y: 0,
Z: "<32 bytes keyed hash>"
}
session accept (S->C)
sent in reply to a session message to indicate session accept and give
a session cookie to the client.
{
A: "session-reply",
B: "<8 bytes random from session request>",
C: "<16 bytes session cookie>",
T: milliseconds_since_epoch_server_now_uint64,
Y: 0,
Z: "<32 bytes keyed hash>"
}
session reject (S->C)
sent in reply to a session message to indicate session rejection
{
A: "session-reject",
B: "<8 bytes random from session request>",
R: "<variable length utf-8 encoded bytes human readable reason here>",
T: milliseconds_since_epoch_server_now_uint64,
Y: 0,
Z: "<32 bytes keyed hash>"
}
spawn a hidden service (C->S)
only one hidden service can be made per session
{
A: "spawn",
C: "<16 bytes session cookie>",
O: config_options_dict,
Y: 1,
Z: "<32 bytes keyed hash>"
}
inform that we have spawned a new hidden service endpoint (S->C)
{
A: "spawn-reply",
C: "<16 bytes session cookie>",
S: ServiceInfo,
Y: 1,
Z: "<32 bytes keyed hash>"
}
inform that we have not spaned a new hidden service endpint (S->C)
after sending this message the server closes the connection
{
A: "spawn-reject",
C: "<16 bytes session cookie>",
E: "<error message goes here>",
Y: 1,
Z: "<32 bytes keyed hash>"
}
create a new convseration on a loki/snode address (C->S)
{
A: "start-convo",
B: "<8 bytes random>",
C: "<16 bytes session cookie>",
R: "human readable remote address .snode/.loki",
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>"
}
sent in reply to a make-convo message to indicate rejection (S->C)
{
A: "start-convo-reject",
B: "<8 bytes random from start-convo message>",
C: "<16 bytes session cookie>",
S: status_bitmask_uint,
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>"
}
sent in reply to a make-convo message to indicate that we have accepted this
new conversation and gives the convo tag it uses.
{
A: "start-convo-accept",
B: "<8 bytes random from start-convo message>",
C: "<16 bytes session cookie>",
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>
}
infrom the status of a converstation on a loki address (S->C)
for an outbund conversation it is sent every time the status bitmask changes.
for inbound convos it is sent immediately when a new inbound conversation is made.
S bit 0 (LSB): we found the introset/endpoint for (set by outbound)
S bit 1: we found the router to align on (set by outbound)
S bit 2: we have a path right now (set by outbound)
S bit 3: we have made the converstation (set by both)
S bit 4: we are an inbound converstation (set by inbound)
{
A: "convo-status",
C: "<16 bytes session cookie>",
R: "human readable address .snode/.loki",
S: bitmask_status_uint64,
T: "<16 bytes convotag>",
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>"
}
send or recieve authenticated data to or from the network (bidi)
protocol numbers are
1 for ipv4
2 for ipv6
{
A: "data",
C: "<16 bytes session cookie>",
T: "<16 bytes convotag>",
W: protocol_number_uint,
X: "<N bytes payload>",
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>"
}
get session information (C->S)
{
A: "info",
C: "<16 bytes session cookie>",
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>"
}
session information update (S->C)
sent in reply to a get session information message
{
A: "info-reply",
C: "<16 bytes session cookie>",
I: hiddenserviceinfo,
Y: sequence_num_uint64,
Z: "<32 bytes keyed hash>"
}
Protocol Flow:
all messages have an A, C, Y and Z value
A is the function name being called
C is the session cookie indicating the current session
Y is the 64 bit message sequence number as an integer
Z is the keyed hash computed by MDS(BE(msg), K) where K is HS(api_password)
with the msg.Z being set to 32 bytes of \x00
both client and server MUST know a variable length string api_password used to
authenticate access to the api subsystem.
the Y value is incremented by 1 for each direction every time the sender sends
a message in that direction.

@ -1,117 +0,0 @@
* lokinet components
** basic data structures
*** AlignedBuffer
*** RouterContact
**** self signed router descriptor
*** Crypto key types
** network / threading / sync utilities
*** threadpool
*** logic (single thread threadpool)
** configuration
*** ini parser
*** llarp::Configuration
** cryptography
*** llarp::Crypto interface
**** libsodium / sntrup implementation
*** llarp::CryptoManager
**** crypto implementation signleton manager
** nodedb
*** llarp_nodedb
**** holds many RouterContacts loaded from disk in memory
**** uses a filesystem skiplist for on disk storage
**** stores in a std::unordered_map addressable via public identity key
** event loop
*** llarp_event_loop
**** udp socket read/write
**** network interface packet read/write
**** stream connection (outbound stream)
**** stream acceptor (inbound stream)
** link layer message transport:
*** ILinkSession
**** the interface for an entity that is single session with relay
**** responsible for delivery recieval of link layer messages in full
*** ILinkLayer
**** bound to an address / interface
**** has a direction, inbound / outbound
**** distinctly identified by the union of interface and direction
**** Holds many ILinkSessions bound on the distinctly idenitfied direction/interface
** link layer messages
*** link layer message parser
**** parses buffers as bencoded dicts
*** link layer message handler
**** handles each type of link layer message
** IHopHandler
*** llarp::PathHopConfig
**** txid, rxid, shared secret at hop
*** llarp::path::Path
**** a built path or a path being built
**** owns a std::vector<PathHopConfig> for each hop's info
*** TransitHop
**** a single hop on a built path
**** has txid, rxid, shared secret, hash of shared secret
** pathset
*** path::Builder
**** builds and maintains a set of paths for a common use
** routing layer message router
*** routing::IMessageHandler
**** interface for routing layer message processing
**** transit hops implement this if they are an endpoint
**** path::Path implement this always
** dht "layer" / rc gossiper
*** TODO rewrite/refactor
** hidden service data structures
*** IntroSet
**** decrypted plaintext hidden service descriptor
*** EncryptedIntroSet
**** public encrpyted / signed version of IntroSet
** service endpoint / outbound context connectivitybackend
*** service::Endpoint
**** backend for sending/recieving packets over the hidden service protocol layer
**** kitchen sink
*** service::SendContext
**** interface type for sending to a resource on the network
*** service::OutboundContext
**** implements SendContext extends path::Builder and path::PathSet
**** for maintaining a pathset that aligns on an introset's intros
~
** snode / exit connectivity backend
*** exit::BaseSession
**** extends path::Builder
**** obtains an exit/snode session from the router they are aligning to
*** exit::Endpoint
**** snode/exit side of an exit::Session
** snapp / exit / mobile / null frontend handlers
*** handlers::TunEndpoint
**** extends service::Endpoint
**** provides tun interface frontend for hidden service backend
*** handlers::ExitEndpoint
**** provides tun interface frontend for exit/snode backend
** outbound message dispatcher
*** TODO tom please document these

@ -1,189 +0,0 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'lokinet'
copyright = '2020, Jeff Becker'
author = 'Jeff Becker'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = ''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['breathe', 'exhale']
breathe_projects = { "lokinet": "@CMAKE_CURRENT_BINARY_DIR@/doxyxml/" }
breathe_default_project = "lokinet"
breathe_domain_by_extension = {"h" : "cpp", "hpp": "cpp"}
exhale_args = {
# These arguments are required
"containmentFolder": "./api",
"rootFileName": "lokinet.rst",
"rootFileTitle": "lokinet internals",
"doxygenStripFromPath": "..",
# Suggested optional arguments
"createTreeView": True,
# TIP: if using the sphinx-bootstrap-theme, you need
# "treeViewIsBootstrap": True,
}
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
source_suffix = ['.rst', '.md']
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
highlight_language = 'c++'
primary_domain = 'cpp'
default_role = 'cpp:any'
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'lokinetdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'lokinet.tex', 'lokinet Documentation',
'Jeff Becker', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'lokinet', 'lokinet Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'lokinet', 'lokinet Documentation',
author, 'lokinet', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']

@ -0,0 +1,9 @@
{
"baseUrl": "",
"indexInFolders": false,
"linkSuffix": ".md",
"mainPageInRoot": false,
"mainPageName": "index",
"linkLowercase": false,
"foldersToGenerate": ["files", "classes", "namespaces"]
}

@ -1,39 +0,0 @@
cryptography:
H(x) is 512 bit blake2b digest of x
HS(x) is 256 bit blake2b digest of x
MD(x, k) is 512 bit blake2b hmac of x with secret value k
MDS(x, k) is 256 bit blake2b hmac of x with secret value k
SE(k, n, x) is chacha20 encrypt data x using symettric key k and nounce n
SD(k, n, x) is chacha20 dectypt data x using symettric key k and nounce n
S(k, x) is sign x with ed25519 using secret key k
EDKG() is generate ec keypair (p, s) public key p (32 bytes), secret key s (64 bytes)
V(k, x, sig) is verify x data using signature sig using public key k
EDDH(a, b) is curve25519 scalar multiplication of a and b
HKE(a, b, x) is hashed key exchange between a and b using a secret key x HS(a + b + EDDH(x, b))
TKE(a, b, x, n) is a transport shared secret kdf using MDS(n, HKE(a, b, x))
when A is client and B is server where n is a 32 bytes shared random
client computes TKE(A.pk, B.pk, A.sk, n)
server computes TKE(A.pk, B.pk, B.sk, n)
PDH(a, b, x) is path shared secret generation HS(a + b + EDDH(x, b))
PKE(a, b, x, n) is a path shared secret kdf using MDS(n, PDH(a, b, x))
given A is the path creator and B is a hop in the path and n is 32 bytes shared random
A computes PKE(A.pk, B.pk, A.sk, n) as S_a
B computes PKE(A.pk, B.pk, B.sk, n) as S_b
S_a is equal to S_b
RAND(n) is n random bytes
PQKG() is generate a sntrup4591761 key pair (sk, pk)
PQKE_A(pk) is alice generating (x, k) where x is sntrup4591761 ciphertext block and k is the session key
PQKE_B(x, sk) is bob calculating k where x is sntrup4591761 ciphertext block, sk is bob's sntrup4591761 secretkey and k is the session key

@ -1,153 +0,0 @@
DHT messages
DHT messages can be either wrapped in a LIDM message or sent anonymously over a path inside a DHT routing message
The distance function is A xor B (traditional kademlia)
The dht implements both iterative and recursive lookups.
Recursive lookups forward the request to a node closer if not found.
Iterative lookups return the key and of the DHT node who is closer.
In the case of iterative FRCM the RC of the closer router is provided.
In the case of iterative FIM the pubkey of a dht node who is closer is provided in the GIM.
find introduction message (FIM)
variant 1: find an IS by SA
{
A: "F",
R: 0 for iterative and 1 for recurisve,
S: "<32 bytes SA>",
T: transaction_id_uint64,
V: 0
}
variant 2: recursively find many IS in a tag
{
A: "F",
E: [list, of, excluded, SA],
R: 0 for iterative and 1 for recurisve,
N: "<16 bytes topic tag>",
T: transaction_id_uint64,
V: 0
}
exclude adding service addresses in E if present
got introduction message (GIM)
sent in reply to FIM containing the result of the search
sent in reply to PIM to acknoledge the publishing of an IS
{
A: "G",
I: [IS, IS, IS, ...],
K: "<32 bytes public key of router who is closer, provided ONLY if FIM.R is 0>",
T: transaction_id_uint64,
V: 0,
}
The I value MUST NOT contain more than 4 IS.
The I value MUST contain either 1 or 0 IS for PIM and FIM variant 1.
publish introduction message (PIM)
publish one IS to the DHT.
version 0 uses the SA of the IS as the keyspace location.
in the future the location will be determined by the dht kdf
which uses a shared random source to obfuscate keyspace location.
R is currently set to 0 by the sender.
{
A: "I",
I: IS,
R: random_walk_counter,
S: optional member 0 for immediate store otherwise non zero,
T: transaction_id_uint64,
V: 0
}
if R is greater than 0, decrement R and forward the request to a random DHT
node without storing the IS.
As of protocol version 0, R is always 0.
If S is provided store the IS for later lookup unconditionally, then
decrement S by 1 and forward to dht peer who is next closest to
the SA of the IS. If S is greater than 3, don't store the IS and
discard this message.
acknoledge introduction message (AIM)
acknoledge the publishing of an introduction
{
A: "A",
P: published_to_counter,
T: transaction_id_uint64,
V: 0
}
increment P by 1 and forward to requester
find router contact message (FRCM)
find a router by long term RC.k public key
{
A: "R",
E: 0 or 1 if exploritory lookup,
I: 0 or 1 if iterative lookup,
K: "<32 byte public key of router>",
T: transaction_id_uint64,
V: 0
}
if E is provided and non zero then return E dht nodes that are closest to K
if I is provided and non zero then this request is considered an iterative lookup
during an iterative lookup the response's GRCM.K is set to the pubkey of the router closer in key space.
during a recursive lookup the request is forwarded to a router who is closer in
keyspace to K.
If we have no peers that are closer to K and we don't have the RC known locally
we reply with a GRCM whose R value is emtpy.
In any case if we have a locally known RC with pubkey equal to K reply with
a GRCM with 1 RC in the R value corrisponding to that locally known RC.
got router contact message (GRCM)
R is a list containing a single RC if found or is an empty list if not found
sent in reply to FRCM only
{
A: "S",
K: "<32 bytes public identity key of router closer, provided ONLY if FRCM.I is 1>",
R: [RC],
T: transaction_id_uint64,
V: 0
}
in response to an exploritory router lookup, where FRCM.E is provided and non zero.
{
A: "S",
N: [list, of, router, publickeys, near, K],
T: transaction_id_uint64,
V: 0
}
sent in reply to a dht request to indicate transaction timeout
{
A: "T",
T: transaction_id_uint64,
V: 0
}

@ -1,94 +0,0 @@
llarp's dht is a recusrive kademlia dht with optional request proxying via paths for requester anonymization.
dht is separated into 2 different networks, one for router contacts, one for introsets.
format for consesus propagation messages:
keys: A, H, K, N, O, T, U, V
concensus request messages
requester requests current table hash, H,N,O is set to zeros if not known
C -> S
{
A: "C",
H: "<32 byte last hash of consensus table>",
N: uint64_number_of_entries_to_request,
O: uint64_offset_in_table,
T: uint64_txid,
V: []
}
when H or N is set to zero from the requester, they are requesting the current consensus table's hash
consensus response message is as follows for a zero H or N value
S -> C
{
A: "C",
H: "<32 byte hash of current consensus table>",
N: uint64_number_of_entries_in_table,
T: uint64_txid,
U: uint64_ms_next_update_required,
V: [proto, major, minor, patch]
}
requester requests a part of the current table for hash H
N must be less than or equal to 512
C -> S
{
A: "C",
H: "<32 bytes current consensus table hash>",
N: 256,
O: 512,
T: uint64_txid,
V: []
}
consensus response message for routers 512 to 512 + 256
S -> C
{
A: "C",
H: "<32 bytes current concensus table hash>",
K: [list, of, N, pubkeys, from, request, starting, at, position, O],
T: uint64_txid,
V: [proto, major, minor, patch]
}
consensus table is a concatination of all public keys in lexigraphical order.
the hash function in use is 256 bit blake2
gossip RC message
broadcast style RC publish message. sent to all peers infrequently.
it is really an unwarrented GRCM, propagate to all peers.
{
A: "S",
R: [RC],
T: 0,
V: proto
}
replays are dropped using a decaying hashset or decaying bloom filter.
the introset dht has 3 message: GetIntroSet Message (GIM), PutIntroSet Message (PIM), FoundIntroSet Message (FIM)

@ -0,0 +1,27 @@
# 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

@ -0,0 +1,6 @@
#!/bin/bash
# apply markdown file content quarks
# rewrite br tags
sed -i 's|<br>|<br/>|g' $@

@ -1,144 +0,0 @@
LLARP - Low Latency Anon Routing Protocol
TL;DR edition: an onion router with a tun interface for transporting ip packets
anonymously between you and the internet and internally inside itself to other users.
This document describes the high level outline of LLARP, specific all the
project's goals, non-goals and network architecture from a bird's eye view.
Preface:
Working on I2P has been a really big learning experience for everyone involved.
After much deliberation I have decided to start making a "next generation" onion
routing protocol. Specifically LLARP is (currently) a research project
to explore the question:
"What if I2P was made in the current year (2018)? What would be different?"
Project Non Goals:
This project does not attempt to solve traffic shape correlation or active nation
state sponsored network attacks. The former is an inherit property of low latency
computer networks that I personally do not think is possible to properly fully
"solve". The latter is a threat that lies outside the scope of what the current
toolset that is available to me at the moment provides.
This project does not attempt to be a magical application level cure-all for
application or end user security. At the end of the day that is a problem that
exists between chair and keyboard.
The Single Project Goal:
LLARP is a protocol suite meant to anonymize IP by providing an anonymous
network level (IPv4/IPv6) tunnel broker for both "hidden services" and
communication back to "the clearnet" (the normal internet). Both hidden service
and clearnet communication MUST permit both outbound and inbound traffic on the
network level without any NAT (except for IPv4 in which NAT is permitted due to
lack of address availability).
In short We want to permit both anonymous exit and entry network level traffic
between LLARP enabled networks and the internet.
Rationale for starting over:
Despite Tor Project's best efforts to popularize Tor use, Tor2Web seems to be
widely popular for people who do not wish to opt into the ecosystem. My proposed
solution would be to permit inbound traffic from "exit nodes" in addition to
allowing outbound exit traffic. I have no ideas on how this could be done with
the existing protocols in Tor or if it is possible or advisable to attempt such
as I am not familiar with their ecosystem.
I2P could have been used as a medium for encrypted anonymous IP transit but the
current network has issues with latency and throughput. Rebasing I2P atop more
modern cryptography has been going on internally inside I2P for at least 5 years
with less progress than desired. Like some before me, I have concluded that it
would be faster to redo the whole stack "the right way" than to wait for I2P to
finish rebasing. That being said, nothing is preventing I2P from be used for
encrypted anonymous IP transit traffic in a future where I2P finishes their
protocol migrations, I just don't want to wait.
In short, I want to take the "best parts" from Tor and I2P and make a new
protocol suite.
For both Tor and I2P I have 2 categories for the attributes they have.
the good
the bad and the ugly
The good (I2P):
I2P aims to provide an anonymous unspoofable load balanced network layer.
I want this feature.
I2P is trust agile, it does not have any hard coded trusts in its network
architecture. Even network boostrap can be done from a single router if the user
desires to (albeit this is currently ill advised).
I want this feature.
The good (Tor):
Tor embraces the reality of the current internet infrastructure by having a
client/server architecture. This allows very low barriers of entry in using the
Tor network and a higher barrier of entry for contributing routing
infrastructure. This promotes a healthy network shape of high capacity servers
serving low capacity clients that "dangle off of the side" of the network.
I want this feature.
The bad and the ugly (I2P):
Bad: I2P uses old cryptography, specially 2048 bit ElGamal using non standard primes.
The use of ElGamal is so pervasive throughout the I2P protocol stack that it
exists at every level of it. Removing it is a massive task that is taking a long
LONG time.
I don't want this feature.
Ugly: I2P cannot currently mitigate most sybil attacks with their current network
architecture. Recently I2P has added some blocklist solutions signed by release
signers but this probably won't scale in the event of a "big" attack. In
addition I2P isn't staffed for such attacks either.
This is a hard problem to solve that the Loki network may be able to help
with by creating a financial barrier to running multiple a relays.
The bad and the ugly (Tor):
Bad: Tor is strictly TCP oriented.
I don't want this feature.

@ -0,0 +1,3 @@
# How Do I use lokinet?
`// TODO: this`

@ -0,0 +1,7 @@
# Lokinet @lokinet_VERSION@ (git rev: @GIT_VERSION@)
summary goes here
## Overview
[code internals](index_namespaces.md)

@ -1,8 +0,0 @@
Welcome to Lokinet Internals
============================
.. toctree::
:maxdepth: 2
api/lokinet

@ -0,0 +1,11 @@
# 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`

@ -1,16 +0,0 @@
Lokinet needs certain capabilities to run to set up a virtual network interface and provide a DNS server. The preferred approach to using this is through the linux capabilities mechanism, which allows assigning limited capabilities without needing to run the entire process as root.
There are two main ways to do this:
1. If you are running lokinet via an init system such as systemd, you can specify the capabilities in the service file by adding:
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
into the [Service] section of the systemd service file. This will assign the necessary permissions when running the process and allow lokinet to work while running as a non-root user.
2. You can set the capabilities on the binary by using the setcap program (if not available you may need to install libcap2-bin on Debian/Ubuntu-based systems) and running:
setcap cap_net_admin,cap_net_bind_service=+eip lokinet
This grants the permissions whenever the lokinet binary is executed.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 414 KiB

@ -1,102 +0,0 @@
digraph {
constants -> util;
crypto -> constants;
crypto -> llarp;
crypto -> util;
dht -> crypto;
dht -> messages;
dht -> llarp;
dht -> path;
dht -> routing;
dht -> service;
dht -> util;
dns -> crypto;
dns -> ev;
dns -> handlers;
dns -> llarp;
dns -> net;
dns -> service;
dns -> util;
ev -> net;
ev -> util;
exit -> crypto;
exit -> handlers;
exit -> messages;
exit -> net;
exit -> path;
exit -> routing;
exit -> util;
handlers -> dns;
handlers -> ev;
handlers -> exit;
handlers -> net;
handlers -> service;
handlers -> util;
link -> constants;
link -> crypto;
link -> ev;
link -> messages;
link -> net;
link -> util;
messages -> crypto;
messages -> dht;
messages -> exit;
messages -> link;
messages -> llarp;
messages -> path;
messages -> routing;
messages -> service;
messages -> util;
net -> crypto;
net -> util;
path -> crypto;
path -> dht;
path -> llarp;
path -> messages;
path -> routing;
path -> service;
path -> util;
routing -> llarp;
routing -> messages;
routing -> path;
routing -> util;
service -> crypto;
service -> dht;
service -> ev;
service -> exit;
service -> handlers;
service -> messages;
service -> net;
service -> path;
service -> routing;
service -> util;
util -> constants;
llarp -> constants;
llarp -> crypto;
llarp -> dht;
llarp -> dns;
llarp -> ev;
llarp -> exit;
llarp -> handlers;
llarp -> link;
llarp -> messages;
llarp -> net;
llarp -> path;
llarp -> routing;
llarp -> service;
llarp -> util;
}

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 368" style="enable-background:new 0 0 1000 368;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:#333333;}
.st3{fill:url(#SVGID_2_);}
.st4{fill:url(#SVGID_3_);}
.st5{fill:#00263A;}
.st6{fill:url(#SVGID_4_);}
.st7{fill:url(#SVGID_5_);}
.st8{fill:url(#SVGID_6_);}
.st9{fill:url(#SVGID_7_);}
.st10{fill:url(#SVGID_8_);}
.st11{fill:url(#SVGID_9_);}
.st12{fill:url(#SVGID_10_);}
.st13{fill:url(#SVGID_11_);}
.st14{fill:url(#SVGID_12_);}
.st15{fill:url(#SVGID_13_);}
.st16{fill:url(#SVGID_14_);}
.st17{fill:url(#SVGID_15_);}
.st18{fill:url(#SVGID_16_);}
.st19{fill:url(#SVGID_17_);}
.st20{opacity:6.000000e-02;}
.st21{opacity:4.000000e-02;fill:#FFFFFF;}
.st22{opacity:7.000000e-02;fill:#FFFFFF;}
.st23{fill:#008522;}
.st24{fill:#78BE20;}
.st25{fill:#005F61;}
.st26{fill:url(#SVGID_18_);}
</style>
<g>
<path class="st0" d="M366.6,78h37.1v178.9H497v32.7H366.6V78z"/>
<path class="st0" d="M619.8,74.5C683.3,74.5,728,120.8,728,184c0,63.1-44.7,109.5-108.2,109.5c-63.5,0-108.2-46.3-108.2-109.5
C511.6,120.8,556.3,74.5,619.8,74.5z M619.8,107.5c-42.8,0-70.1,32.7-70.1,76.5c0,43.5,27.3,76.5,70.1,76.5
c42.5,0,70.1-33,70.1-76.5C689.9,140.2,662.3,107.5,619.8,107.5z"/>
<path class="st0" d="M819.4,200.5L801,222v67.6h-37.1V78H801v100.9L883.8,78h46l-86,99.9l92.3,111.7h-45.7L819.4,200.5z"/>
<path class="st0" d="M960.9,78H998v211.6h-37.1V78z"/>
</g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="86.8402" y1="268.7968" x2="86.8402" y2="0.426">
<stop offset="0" style="stop-color:#78BE20"/>
<stop offset="0.1197" style="stop-color:#58AF21"/>
<stop offset="0.3682" style="stop-color:#199122"/>
<stop offset="0.486" style="stop-color:#008522"/>
<stop offset="0.6925" style="stop-color:#007242"/>
<stop offset="0.8806" style="stop-color:#006459"/>
<stop offset="1" style="stop-color:#005F61"/>
</linearGradient>
<polygon class="st1" points="132.1,268.8 0.3,137 136.9,0.4 173.3,36.8 73.1,137 168.5,232.4 "/>
</g>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="212.9564" y1="367.5197" x2="212.9564" y2="99.1484">
<stop offset="0" style="stop-color:#78BE20"/>
<stop offset="0.1197" style="stop-color:#58AF21"/>
<stop offset="0.3682" style="stop-color:#199122"/>
<stop offset="0.486" style="stop-color:#008522"/>
<stop offset="0.6925" style="stop-color:#007242"/>
<stop offset="0.8806" style="stop-color:#006459"/>
<stop offset="1" style="stop-color:#005F61"/>
</linearGradient>
<polygon class="st3" points="162.9,367.5 126.5,331.1 226.7,230.9 131.3,135.6 167.7,99.1 299.5,230.9 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

@ -1,70 +0,0 @@
LokiNET admin api
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
------
the admin api currently uses jsonrpc 2.0 over http
the methods currently provided are:
llarp.nodedb.rc.getbykey
get rc by public identity key
required parameters:
key: 32 bytes public identity key
returns:
a list of RCs (see protocol v0 spec) that have this public identity key
usually 0 or 1 RCs
llarp.nodedb.rc.getbycidr
get a list of RCs in an address range
required parameters:
cidr: ipv6 network cidr string, i.e. "::ffff.21.0.0.0/8" or "fc00::/7"
limit: integer max number of items to fetch, zero or positive integer,
if zero no limit.
returns:
a list of 0 to limit RCs that advertise themselves as being reachble via an
address in the given CIDR.
llarp.admin.sys.uptime (authentication required)
required paramters:
(none)
returns:
an integer milliseconds since unix epoch we've been online
llarp.admin.link.neighboors
get a list of connected service nodes on all links
required parameters:
(none)
returns:
list of 0 to N dicts in the following format:
{
"connected" : uint64_milliseconds_timestamp_connected_at
"ident" : "<64 hex encoded public identity key>",
"laddr" : "local address",
"raddr" : "remote address"
}

@ -0,0 +1,5 @@
site_name: Lokinet
theme:
name: 'readthedocs'
docs_dir: markdown
site_dir: html

@ -0,0 +1,35 @@
# 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.

@ -1,90 +0,0 @@
Wire Protocol (version ½)
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
LLARP supports by default an authenticated and framed transport over UTP [1]
Handshake:
Alice establishes a UTP "connection" with Bob.
Alice sends a LIM a_L encrpyted with the initial b_K key
if Bob accepts Alice's router, Bob replies with a LIM b_L encrpyted with the
b_K key.
next the session keys are generated via:
a_h = HS(a_K + a_L.n)
b_h = HS(b_K + b_L.n)
a_K = TKE(A.p, B_a.e, sk, a_h)
b_K = TKE(A.p, B_a.e, sk, b_h)
A.tx_K = b_K
A.rx_K = a_K
B.tx_K = a_K
B.rx_K = B_K
the initial value of a_K is HS(A.k) and b_K is HS(B.k)
608 byte fragments are sent over UTP in an ordered fashion.
The each fragment F has the following structure:
[ 32 bytes blake2 keyed hash of the following 576 bytes (h)]
[ 32 bytes random nonce (n)]
[ 544 bytes encrypted payload (p)]
the recipiant verifies F.h == MDS(F.n + F.p, rx_K) and the UTP session
is reset if verification fails.
the decrypted payload P has the following structure:
[ 24 bytes random (A) ]
[ big endian unsigned 32 bit message id (I) ]
[ big endian unsigned 16 bit fragment length (N) ]
[ big endian unsigned 16 bit fragment remaining bytes (R) ]
[ N bytes of plaintext payload (X) ]
[ trailing bytes discarded ]
link layer messages fragmented and delievered in any order the sender chooses.
recipaint ensures a buffer for message number P.I exists, allocating one if it
does not exist.
recipiant appends P.X to the end of the buffer for message P.I
if P.R is zero then message number P.I is completed and processed as a link
layer messages. otherwise the recipiant expects P.R additional bytes.
P.R's value MUST decrease by P.N in the next fragment sent.
message size MUST NOT exceed 8192 bytes.
if a message is not received in 2 seconds it is discarded and any further
fragments for the message are also discarded.
P.I MUST have the initial value 0
P.I MUST be incremeneted by 1 for each new messsage transmitted
P.I MAY wrap around back to 0
after every fragment F the session key K is mutated via:
K = HS(K + P.A)
Periodically the connection initiator MUST renegotiate the session key by
sending a LIM after L.p milliseconds have elapsed.
If the local RC changes while a connection is established they MUST
renegotioate the session keys by sending a LIM to ensure the new RC is sent.
references:
[1] http://www.bittorrent.org/beps/bep_0029.html

@ -1,903 +0,0 @@
LLARP v0
LLARP (Low Latency Anon Routing Protocol) is a protocol for anonymizing senders and
recipiants of encrypted messages sent over the internet without a centralised
trusted party.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
basic structures:
all structures are key, value dictionaries encoded with bittorrent encoding
notation:
a + b is a concatanated with b
a ^ b is a bitwise XOR b
x[a:b] is a memory slice of x from index a to b
BE(x) is bittorrent encode x
BD(x) is bittorrent decode x
{ a: b, y: z } is a dictionary with two keys a and y
whose values are b and z respectively
[ a, b, c ... ] is a list containing a b c and more items in that order
"<description>" is a bytestring whose contents and length is described by the
quoted value <description>
"<value>" * N is a bytestring containing the <value> concatenated N times.
cryptography:
see crypto_v0.txt
---
wire protocol
see wire-protocol.txt
---
datastructures:
all datastructures are assumed version 0 if they lack a v value
otherwise version is provided by the v value
all ip addresses can be ipv4 via hybrid dual stack ipv4 mapped ipv6 addresses,
i.e ::ffff.8.8.8.8. The underlying implementation MAY implement ipv4 as native
ipv4 instead of using a hybrid dual stack.
net address:
net addresses are a variable length byte string, if between 7 and 15 bytes it's
treated as a dot notation ipv4 address (xxx.xxx.xxx.xxx)
if it's exactly 16 bytes it's treated as a big endian encoding ipv6 address.
address info (AI)
An address info (AI) defines a publically reachable endpoint
{
c: transport_rank_uint16,
d: "<transport dialect name>",
e: "<32 bytes public encryption key>",
i: "<net address>",
p: port_uint16,
v: 0
}
example wank address info:
{
c: 1,
d: "wank",
e: "<32 bytes of 0x61>",
i: "123.123.123.123",
p: 1234,
v: 0
}
bencoded form:
d1:ci1e1:d4:wank1:e32:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1:d3:iwp1:i15:123.123.123.1231:pi1234e1:vi0ee
Traffic Policy (TP)
Traffic policy (TP) defines port, protocol and QoS/drop policy.
{
a: protocol_integer,
b: port_integeger,
d: drop_optional_integer,
v: 0
}
drop d is set to 1 to indicate that packets of protocol a with source port b will be dropped.
if d is 0 or not provided this traffic policy does nothing.
Exit Info (XI)
An exit info (XI) defines a exit address that can relay exit traffic to the
internet.
{
a: "<net address exit address>",
b: "<net address exit netmask>",
k: "<32 bytes public encryption/signing key>",
p: [ list, of, traffic, policies],
v: 0
}
Exit Route (XR)
An exit route (XR) define an allocated exit address and any additional
information required to access the internet via that exit address.
{
a: "<16 bytes big endian ipv6 gateway address>",
b: "<16 bytes big endian ipv6 netmask>",
c: "<16 bytes big endian ipv6 source address>",
l: lifetime_in_milliseconds_uint64,
v: 0
}
router contact (RC)
router's full identity
{
a: [ one, or, many, AI, here ... ],
e: extensions_supported,
i: "<max 8 bytes network identifier>",
k: "<32 bytes public long term identity signing key>",
n: "<optional max 32 bytes router nickname>",
p: "<32 bytes public path encryption key>",
s: [services, supported],
u: time_signed_at_milliseconds_since_epoch_uint64,
v: 0,
x: [ Exit, Infos ],
z: "<64 bytes signature using identity key>"
}
e is a dict containing key/value of supported protocol extensions on this service node, keys are strings, values MUST be 0 or 1.
s is a list of services on this service node:
each service is a 6 item long, list of the following:
["_service", "_proto", ttl_uint, priority_uint, weight_uint, port_uint]
with the corrisponding SRV record:
<_service>.<_proto>.router_pubkey_goes_here.snode. <ttl_uint> IN SRV <priority_uint> <weight_uint> <port_uint> router_pubkey_goes_here.snode
RC.t is the timestamp of when this RC was signed.
RC is valid for a maximum of 1 hour after which it MUST be resigned with the new
timestamp.
RC.i is set to the network identifier.
only routers with the same network identifier may connect to each other.
"testnet" for testnet.
"lokinet" for the "official" lokinet mainline network.
other values of RC.i indicate the router belongs to a network fork.
service info (SI)
public information blob for a hidden service
e is the long term public encryption key
s is the long term public signing key
v is the protocol version
x is a nounce value for generating vanity addresses that can be omitted
if x is included it MUST be equal to 16 bytes
{
e: "<32 bytes public encryption key>",
s: "<32 bytes public signing key>",
v: 0,
x: "<optional 16 bytes nonce for vanity>"
}
service address (SA)
the "network address" of a hidden service, which is computed as the blake2b
256 bit hash of the public infomration blob.
HS(BE(SI))
when in string form it's encoded with z-base32 and uses the .loki tld
introduction (I)
a descriptor annoucing a path to a hidden service
k is the rc.k value of the router to contact
p is the path id on the router that is owned by the service
v is the protocol version
x is the timestamp milliseconds since epoch that this introduction expires at
{
k: "<32 bytes public identity key of router>",
l: advertised_path_latency_ms_uint64, (optional)
p: "<16 bytes path id>",
v: 0,
x: time_expires_milliseconds_since_epoch_uint64
}
introduction set (IS)
and introset is a signed set of introductions for a hidden service
a is the service info of the publisher
h is a list of srv records in same format as in RCs
i is the list of introductions that this service is advertising with
k is the public key to use when doing encryption to this hidden service
n is a 16 byte null padded utf-8 encoded string tagging the hidden service in
a topic searchable via a lookup (optional)
v is the protocol version
w is an optinal proof of work for DoS protection (slot for future)
z is the signature of the entire IS where z is set to zero signed by the hidden
service's signing key.
{
a: SI,
h: [list, of, advertised, services],
i: [ I, I, I, ... ],
k: "<1218 bytes sntrup4591761 public key block>",
n: "<16 bytes service topic (optional)>",
t: timestamp_uint64_milliseconds_since_epoch_published_at,
v: 0,
w: optional proof of work,
z: "<64 bytes signature using service info signing key>"
}
h is a list of services on this endpoint
each service is a 7 item long, list of the following:
["_service", "_proto", ttl_uint, priority_uint, weight_uint, port_uint, "<32 bytes SA of the service>"]
with the corrisponding SRV record:
<_service>.<_proto>.<service_address>.loki. <ttl_uint> IN SRV <priority_uint> <weight_uint> <port_uint> <SA of sub service>.loki.
recursion on SRV records is NOT permitted.
---
Encrypted frames:
Encrypted frames are encrypted containers for link message records like LRCR.
32 bytes hmac, h
32 bytes nounce, n
32 bytes ephmeral sender's public encryption key, k
remaining bytes ciphertext, x
decryption:
0) verify hmac
S = PKE(n, k, our_RC.K)
verify h == MDS(n + k + x, S)
If the hmac verification fails the entire parent message is discarded
1) decrypt and decode
new_x = SD(S, n[0:24], x)
msg = BD(new_x)
If the decoding fails the entire parent message is discarded
encryption:
to encrypt a frame to a router with public key B.k
0) prepare nounce n, ephemeral keypair (A.k, s) and derive shared secret S
A.k, s = ECKG()
n = RAND(32)
S = PKE(p, A.k, B.k, n)
1) encode and encrypt
x = BE(msg)
new_x = SE(S, n[0:24], x)
2) generate message authentication
h = MDS(n + A.k + new_x, S)
resulting frame is h + n + A.k + new_x
---
link layer messages:
the link layer is responsible for anonymising the source and destination of
routing layer messages.
any link layer message without a key v is assumed to be version 0 otherwise
indicates the protocol version in use.
link introduce message (LIM)
This message MUST be the first link message sent before any others. This message
identifies the sender as having the RC contained in r. The recipiant MUST
validate the RC's signature and ensure that the public key in use is listed in
the RC.a matching the ipv6 address it originated from.
{
a: "i",
e: "<32 bytes ephemeral public encryption key>",
n: "<32 bytes nonce for key exhcange>",
p: uint64_milliseconds_session_period,
r: RC,
v: 0,
z: "<64 bytes signature of entire message by r.k>"
}
the link session will be kept open for p milliseconds after which
the session MUST be renegotiated.
link relay commit message (LRCM)
request a commit to relay traffic to another node.
{
a: "c",
c: [ list, of, encrypted, LRCR frames ],
v: 0
}
c MUST contain dummy records if the hop length is less than the maximum
hop length.
link relay commit record (LRCR)
record requesting relaying messages for 600 seconds to router
on network whose i is equal to RC.k and decrypt data any messages using
PKE(n, rc.p, c) as symmetric key for encryption and decryption.
if l is provided and is less than 600 and greater than 10 then that lifespan
is used (in seconds) instead of 600 seconds.
if w is provided and fits the required proof of work then the lifetime of
the path is extended by w.y seconds
{
c: "<32 byte public encryption key used for upstream>",
d: uint_optional_ms_delay, // TODO
i: "<32 byte RC.k of next hop>",
l: uint_optional_lifespan,
n: "<32 bytes nounce for key exchange>",
r: "<16 bytes rx path id>",
t: "<16 bytes tx path id>",
u: "<optional RC of the next hop>",
v: 0,
w: proof of work
}
w if provided is a dict with the following struct
{
t: time_created_seconds_since_epoch,
v: 0,
y: uint32_seconds_extended_lifetime,
z: "<32 bytes nonce>"
}
the validity of the proof of work is that given
h = HS(BE(w))
h has log_e(y) prefixed bytes being 0x00
this proof of work requirement is subject to change
if i is equal to RC.k then any LRDM.x values are decrypted and interpreted as
routing layer messages. This indicates that we are the farthest hop in the path.
link relay status message (LRSM)
response to path creator about build status
{
a: "s",
c: [ list, of, encrypted, LRSR frames],
p: "<16 bytes rx path id>",
s: uint_status_flags, // for now, only set (or don't) success bit
v: 0
}
the creator of the LRSM MUST include dummy LRSR records
to pad out to the maximum path length
link relay status record (LRSR)
record indicating status of path build
{
s: uint_status_flags,
v: 0
}
uint_status_flags (bitwise booleans):
[0] = success
[1] = fail, hop timeout
[2] = fail, congestion
[3] = fail, refusal, next hop is not known to be a snode
[4] = fail, used by hop creator when decrypting frames if decryption fails
[5] = fail, used by hop creator when record decode fails
[4-63] reserved
link relay upstream message (LRUM)
sent to relay data via upstream direction of a previously created path.
{
a: "u",
p: "<16 bytes path id>",
v: 0,
x: "<N bytes encrypted x1 value>",
y: "<32 bytes nonce>"
}
x1 = SD(k, y, x)
if we are the farthest hop, process x1 as a routing message
otherwise transmit a LRUM to the next hop
{
a: "u",
p: p,
v: 0,
x: x1,
y: y ^ HS(k)
}
link relay downstream message (LRDM)
sent to relay data via downstream direction of a previously created path.
{
a: "d",
p: "<16 bytes path id>",
v: 0,
x: "<N bytes encrypted x1 value>",
y: "<32 bytes nonce>"
}
if we are the creator of the path decrypt x for each hop key k
x = SD(k, y, x)
otherwise transmit LRDM to next hop
x1 = SE(k, y, x)
{
a: "d",
p: p,
v: 0,
x: x1,
y: y ^ HS(k)
}
link immediate dht message (LIDM):
transfer one or more dht messages directly without a previously made path.
{
a: "m",
m: [many, dht, messages],
v: 0
}
link immediate state message (LISM)
transfer a state message between nodes
{
a: "s",
s: state_message,
v: 0
}
---
state message:
NOTE: this message type is currently a documentation stub and remains unimplemented.
state messages propagate changes to the service nodes concensous to thin clients.
service node joined network
{
A: "J",
R: "<32 bytes public key>",
V: 0
}
service node parted network
{
A: "P",
R: "<32 bytes public key>",
V: 0
}
service node list request
request the service node list starting at index I containing R entries
{
A: "R",
I: starting_offset_int,
R: number_of_entires_to_request_int,
V: 0
}
service node list response
response to service node list request
{
A: "L",
S: {
"<32 bytes pubkey>" : 1,
"<32 bytes pubkey>" : 1,
....
},
V: 0
}
---
routing layer:
the routing layer provides inter network communication between the LLARP link
layer and ip (internet protocol) for exit traffic or ap (anonymous protocol) for
hidden services. replies to messages are sent back via the path they
originated from inside a LRDM. all routing messages have an S value which
provides the sequence number of the message so the messages can be ordered.
ipv4 addresses are allowed via ipv4 mapped ipv6 addresses, i.e. ::ffff.10.0.0.1
path confirm message (PCM)
sent as the first message down a path after it's built to confirm it was built
always the first message sent
{
A: "P",
L: uint64_milliseconds_path_lifetime,
S: 0,
T: uint64_milliseconds_sent_timestamp,
V: 0
}
path latency message (PLM)
a latency measurement message, reply with a PLM response if we are the far end
of a path.
variant 1, request, generated by the path creator:
{
A: "L",
S: uint64_sequence_number,
V: 0
}
variant 2, response, generated by the endpoint that recieved the request.
{
A: "L",
S: uint64_sequence_number,
T: uint64_timestamp_recieved,
V: 0
}
obtain exit message (OXM)
sent to an exit router to obtain ip exit traffic context.
replies are sent down the path that messages originate from.
{
A: "X",
B: [list, of, permitted, blacklisted, traffic, policies],
E: 0 for snode communication or 1 for internet access,
I: "<32 bytes signing public key for future communication>",
S: uint64_sequence_number,
T: uint64_transaction_id,
V: 0,
W: [list, of, required, whitelisted, traffic, policies],
X: lifetime_of_address_mapping_in_seconds_uint64,
Z: "<64 bytes signature using I>"
}
grant exit messsage (GXM)
sent in response to an OXM to grant an ip for exit traffic from an external
ip address used for exit traffic.
{
A: "G",
S: uint64_sequence_number,
T: transaction_id_uint64,
Y: "<16 byte nonce>",
V: 0,
Z: "<64 bytes signature>"
}
any TITM recieved on the same path will be forwarded out to the internet if
OXAM.E is not 0, otherwise it is interpreted as service node traffic.
reject exit message (RXM)
sent in response to an OXAM to indicate that exit traffic is not allowed or
was denied.
{
A: "J",
B: backoff_milliseconds_uint64,
R: [list, of, rejected, traffic, policies],
S: uint64_sequence_number,
T: transaction_id_uint64,
V: 0,
Y: "<16 byte nonce>",
Z: "<64 bytes signature>"
}
discarded data fragment message (DDFM)
sent in reply to TDFM when we don't have a path locally or are doing network
congestion control from a TITM.
{
A: "D",
P: "<16 bytes path id>",
S: uint64_sequence_number_of_fragment_dropped,
V: 0
}
transfer data fragment message (TDFM)
transfer data between paths.
{
A: "T",
P: "<16 bytes path id>",
S: uint64_sequence_number,
T: hidden_service_frame,
V: 0
}
transfer data to another path with id P on the local router place a random 32
byte and T values into y and z values into a LRDM message (respectively) and
send it in the downstream direction. if this path does not exist on the router
it is replied to with a DDFM.
hidden service data (HSD)
data sent anonymously over the network to a recipiant from a sender.
sent inside a HSFM encrypted with a shared secret.
{
a: protocol_number_uint,
d: "<N bytes payload>",
i: Introduction for reply,
n: uint_message_sequence_number,
o: N seconds until this converstation plans terminate,
s: SI of sender,
t: "<16 bytes converstation_tag>,
v: 0
}
hidden service frame (HSF)
TODO: document this better
establish converstation tag message (variant 1)
generate a new convotag that is contained inside an encrypted HSD
{
A: "H",
C: "<1048 bytes ciphertext block>",
D: "<N bytes encrypted HSD>",
F: "<16 bytes source path_id>",
N: "<32 bytes nonce for key exchange>",
V: 0,
Z: "<64 bytes signature of entire message using sender's signing key>"
}
alice (A) wants to talk to bob (B) over the network, both have hidden services
set up and are online on the network.
A and B are both referring to alice and bob's SI respectively.
A_sk is alice's private signing key.
for alice (A) to send the string "beep" to bob (B), alice picks an introduction
to use on one of her paths (I_A) such that I_A is aligning with one of bobs's
paths (I_B)
alice generates:
T = RAND(16)
m = {
a: 0,
d: "beep",
i: I_A,
n: 0,
s: A,
t: T,
v: 0
}
X = BE(m)
C, K = PQKE_A(I_B.k)
N = RAND(32)
D = SE(X, K, N)
path = PickSendPath()
M = {
A: "T",
P: I_B.P,
S: uint64_sequence_number,
T: {
A: "H",
C: C,
D: D,
F: path.lastHop.txID,
N: N,
V: 0,
Z: "\x00" * 64
},
V: 0
}
Z = S(A_sk, BE(M))
alice transmits a TDFM to router with public key I_B.K via her path that ends
with router with public key I_B.k
path = PickSendPath()
{
A: "T",
P: I_B.P,
S: uint64_sequence_number,
T: {
A: "H",
C: C,
D: D,
F: path.lastHop.txID,
N: N,
V: 0,
Z: Z
},
V: 0
}
the shared secret (S) for further message encryption is:
S = HS(K + PKE(A, B, sk, N))
given sk is the local secret encryption key used by the current hidden service
please note:
signature verification of the outer message can only be done after decryption
because the signing keys are inside the encrypted HSD.
data from a previously made session (variant 2)
transfer data on a converstation previously made
{
A: "H",
D: "<N bytes encrypted HSD>",
F: "<16 bytes path id of soruce>",
N: "<32 bytes nonce for symettric cipher>",
T: "<16 bytes converstation tag>",
V: 0,
Z: "<64 bytes signature using sender's signing key>"
}
reject a message sent on a convo tag, when a remote endpoint
sends this message a new converstation SHOULD be established.
{
A: "H",
F: "<16 bytes path id of soruce>",
R: 1,
T: "<16 bytes converstation tag>",
V: 0,
Z: "<64 bytes signature using sender's signing key>"
}
transfer ip traffic message (TITM)
transfer ip traffic
{
A: "I",
S: uint64_sequence_number,
V: 0,
X: [list, of, ip, packet, buffers],
}
an ip packet buffer is prefixed with a 64 bit big endian unsigned integer
denoting the sequence number for re-ordering followed by the ip packet itself.
X is parsed as a list of IP packet buffers.
for each ip packet the source addresss is extracted and sent on the
appropriate network interface.
When we receive an ip packet from the internet to an exit address, we put it
into a TITM, and send it downstream the corresponding path in an LRDM.
update exit path message (UXPM)
sent from a new path by client to indicate that a previously established exit
should use the new path that this message came from.
{
A: "U",
P: "<16 bytes previous tx path id>",
S: uint64_sequence_number,
T: uint64_txid,
V: 0,
Y: "<16 bytes nonce>",
Z: "<64 bytes signature using previously provided signing key>"
}
close exit path message (CXPM)
client sends a CXPM when the exit is no longer needed or by the exit if the
exit wants to close prematurely.
also sent by exit in reply to a CXPM to confirm close.
{
A: "C",
S: uint64_sequence_number,
V: 0,
Y: "<16 bytes nonce>",
Z: "<64 bytes signature>"
}
update exit verify message (EXVM)
sent in reply to a UXPM to verify that the path handover was accepted
{
A: "V",
S: uint64_sequence_number,
T: uint64_txid,
V: 0,
Y: "<16 bytes nonce>",
Z: "<64 bytes signature>"
}
DHT message holder message:
wrapper message for sending many dht messages down a path.
{
A: "M",
M: [many, dht, messages, here],
S: uint64_sequence_number,
V: 0
}

@ -0,0 +1,25 @@
# Lokinet Docs
This is where Lokinet documentation lives.
## High level
[How is Lokinet different to \[insert network technology name here\] ?](net-comparisons.md)
[How Do I use Lokinet?](ideal-ux.md)
[What Lokinet can't do](we-cannot-make-sandwiches.md)
## Lokinet (SN)Application Developer Portal
[What are "SNApps" and how to develop them.](snapps-dev-guide.md)
[How do I embed lokinet into my application?](liblokinet-dev-guide.md)
## Lokinet Internals
[Build Doxygen Docs for internals](doxygen.md)

@ -1,47 +0,0 @@
# snapp config options
## required
### ifname
network interface name
### ifaddr
ip range of network interface
## optional
### keyfile
the private key to persist address with.
if not specified the address will be ephemeral.
### reachable
bool value that sets if we publish our snapp to the dht
`true`: we are reachable via dht
`false`: we are not reachable via dht
### hops
number of hops in a path, min is `1`, max is `8`, defaults to `4`
### paths
number of paths to maintain at any given time, defaults to `6`.
### blacklist-snode
adds a `.snode` to path build blacklist
### exit-node
specify a `.snode` or `.loki` address to use as an exit broker
### local-dns
address to bind local dns resoler to, defaults to `127.3.2.1:53`
if port is omitted it uses port `53`
### upstream-dns
address to forward non lokinet related queries to. if not set lokinet dns will reply with srvfail.
### mapaddr
perma map `.loki` address to an ip owned by the snapp
to map `whatever.loki` to `10.0.10.10` it can be specified via:
```
mapaddr=whatever.loki:10.0.10.10
```
## compile time optional features
### on-up
path to shell script to call when our interface is up
### on-down
path to shell script to call when our interface is down
### on-ready
path to shell script to call when snapp is first ready

@ -0,0 +1,30 @@
# (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`

@ -1,38 +0,0 @@
What is a RouterEvent?
A RouterEvent is a way of representing a conceptual event that took place in a "router" (relay or client).
RouterEvents are used in order to collect information about a network all in one place and preserve causality.
How do I make a new RouterEvent?
Add your event following the structure in llarp/tooling/router_event.{hpp,cpp}
Add your event to pybind in pybind/llarp/tooling/router_event.cpp
What if a class my event uses is missing members in pybind?
Find the relevant file pybind/whatever/class.cpp and remedy that!
What if I need to refer to classes which aren't available already in pybind?
Add pybind/namespace/namespace/etc/class.cpp and pybind it!
You will need to edit the following files accordingly:
pybind/common.hpp
pybind/module.cpp
pybind/CMakeLists.txt
How do I use a RouterEvent?
From the cpp side, find the place in the code where it makes the most logical sense
that the conceptual event has taken place (and you have the relevant information to
create the "event" instance) and create it there as follows:
#include <llarp/tooling/relevant_event_header.hpp>
where the event takes place, do the following:
auto event = std::make_unique<event_type_here>(constructor_args...);
somehow_get_a_router->NotifyRouterEvent(std::move(event));
From the Python side...it's a python object!

@ -0,0 +1,19 @@
# 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.

@ -1,309 +0,0 @@
Wire Protocol (version 1)
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
LLARP's wire protocol is Internet Wire Protocol (IWP)
The main goal of iwp is to provide an authenticated encrypted
reliable semi-ordered durable datagram transfer protocol supporting
datagrams of larger size than link mtu.
in iwp there is an initiator who initiates a session to a recipiant.
iwp has 3 phases. the first phase is the proof of flow phase.
the second is a session handshake phase, the third is data transmission.
proof of flow:
the purpose of the proof of flow phase is to verify the existence
of the initiator's endpoint.
At any time before the data transfer phase a reject message
is sent the session is reset.
Alice (A) is the sender and Bob (B) is the recipiant.
A asks for a flow id from B.
B MAY send a flow id to A or MAY reject the message from A.
session handshake:
an encrypted session is established using establish wire session messages
using a newly created flow id.
message format:
there are 2 layers in this protocol, outer messages and inner messages.
outer messages are sent in plaintext and / or obfsucated with symettric
encryption using a preshared key.
inner messages are inside an encrypted and authenticated envelope
wrapped by an outer messages, which is always a data tranmssion message.
outer message format:
every outer message MAY be obfsucated via symettric encryption for dpi
resistance reasons, this is not authenticated encryption.
the message is first assumed to be sent in clear first.
if parsing of clear variant fails then the recipiant MUST fall back to assuming
the protocol is in obfuscated mode.
<16 bytes nounce, n>
<remaining bytes obsfucated, m>
obfuscated via:
K = HS(B_k)
N = HS(n + K)
X = SD(K, m, N[0:24])
where
B_k is the long term identity public key of the recipient.
HS is blake2 256 bit non keyed hash
SD is xchacha20 symettric stream cipher (decryption)
outer-header:
<1 byte command>
<1 byte reserved set to 0x3d>
command 'O' - obtain flow id
obtain a flow id
<outer-header>
<6 magic bytes "netid?">
<8 bytes netid, I>
<8 bytes timestamp milliseconds since epoch, T>
<32 bytes public identity key of sender, A_k>
<0-N bytes discarded>
<last 64 bytes signature of unobfuscated packet, Z>
the if the network id differs from the current network's id a reject message
MUST be sent
MUST be replied to with a message rejected or a give handshake cookie
command 'G' - give flow id
<outer-header>
<6 magic bytes "netid!">
<16 bytes new flow id>
<32 bytes public identiy key of sender, A_k>
<0-N bytes ignored but included in signature>
<last 64 bytes signature of unobfsucated packet, Z>
after recieving a give flow id message a session negotiation can happen with that flow id.
command 'R' - flow rejected
reject new flow
<outer-header>
<14 ascii bytes reason for rejection null padded>
<8 bytes timestamp>
<32 bytes public identity key of sender, A_k>
<0-N bytes ignored but included in signature>
<last 64 bytes signature of unobsfucated packet, Z>
command 'E' - establish wire session
establish an encrypted session using a flow id
<outer-header>
<2 bytes 0x0a 0x0d>
<4 bytes flags, F>
<16 bytes flow id, B>
<32 bytes ephemeral public encryption key, E>
<8 bytes packet counter starting at 0>
<optional 32 bytes authenticated credentials, A>
<last 64 bytes signature of unobfuscated packet using identity key, Z>
F is currently set to all zeros
every time we try establishing a wire session we increment the counter
by 1 for the next message we send.
when we get an establish wire session message
we reply with an establish wire session message with counter being counter + 1
if A is provided that is interpreted as being generated via:
h0 = HS('<insert some password here>')
h1 = EDDH(us, them)
A = HS(B + h0 + h1)
each side establishes their own rx key using this message.
when each side has both established thier rx key data can be transmitted.
command 'D' - encrypted data transmission
transmit encrypted data on a wire session
<outer-header>
<16 bytes flow-id, F>
<24 bytes nonce, N>
<N encrypted data, X>
<last 32 bytes keyed hash of entire payload, Z>
B is the flow id from the recipiant (from outer header)
N is a random nounce
X is encrypted data
Z is keyed hash of entire message
Z is generated via:
msg.Z = MDS(outer-header + F + N + X, tx_K)
data tranmission:
inner message format of X (after decryption):
inner header:
<1 byte protocol version>
<1 byte command>
command: 'k' (keep alive)
tell other side to acknoledge they are alive
<inner header>
<2 bytes resevered, set to 0>
<2 bytes attempt counter, set to 0 and incremented every retransmit, reset when we get a keepalive ack>
<2 bytes milliseconds ping timeout>
<8 bytes current session TX limit in bytes per second>
<8 bytes current session RX use in bytes per second>
<8 bytes milliseconds since epoch our current time>
<remaining bytes discarded>
command: 'l' (keep alive ack)
acknolege keep alive message
<inner header>
<6 bytes reserved, set to 0>
<8 bytes current session RX limit in bytes per second>
<8 bytes current session TX use in bytes per second>
<8 bytes milliseconds since epoch our current time>
<remaining bytes discarded>
command: 'n' (advertise neighboors)
tell peer about neighboors, only sent by non service nodes to other non service
nodes.
<inner header>
<route between us and them>
<0 or more intermediate routes>
<route from a service node>
route:
<1 byte route version (currently 0)>
<1 byte flags, lsb set indicates src is a service node>
<2 bytes latency in ms>
<2 bytes backpressure>
<2 bytes number of connected peers>
<8 bytes publish timestamp ms since epoch>
<32 bytes pubkey neighboor>
<32 bytes pubkey src>
<64 bytes signature of entire route signed by src>
command: 'c' (congestion)
tell other side to slow down
<inner header>
<2 bytes reduce TX rate by this many 1024 bytes per second>
<4 bytes milliseconds slowdown lifetime>
<remaining bytes discarded>
command: 'd' (anti-congestion)
tell other side to speed up
<inner header>
<2 bytes increase TX rate by this many 1024 bytes per second>
<4 bytes milliseconds speedup lifetime>
<remaining bytes discarded>
command: 's' (start transmission)
initate the transmission of a message to the remote peer
<inner header>
<1 byte flags F>
<1 byte reserved R set to zero>
<2 bytes total size of full message>
<4 bytes sequence number S>
<32 bytes blake2 hash of full message>
<N remaining bytes first fragment of message>
if F lsb is set then there is no further fragments
command: 't' (continued transmission)
continue transmission of a bigger message
<inner header>
<1 byte flags F>
<1 bytes reserved R set to zero>
<2 bytes 16 byte block offset in message>
<4 bytes sequence number S>
<N remaining bytes fragment of message aligned to 16 bytes>
<remaining bytes not aligned to 16 bytes discarded>
command: 'q' (acknoledge transmission)
acknoledges a transmitted message
command: 'r' (rotate keys)
inform remote that their RX key should be rotated
given alice(A) sends this message to bob(B) the new keys are computed as such:
n_K = TKE(K, B_e, K_seed, N)
A.tx_K = n_K
B.rx_K = n_K
<inner header>
<2 bytes milliseconds lifetime of old keys, retain them for this long and then discard>
<4 bytes reserved, set to 0>
<32 bytes key exchange nounce, N>
<32 bytes next public encryption key, K>
<remaining bytes discarded>
command: 'u' (upgrade)
request protocol upgrade
<inner header>
<1 byte protocol min version to upgrade to>
<1 byte protocol max version to upgrade to>
<remaining bytes discarded>
command: 'v' (version upgrade)
sent in response to upgrade message
<inner header>
<1 byte protocol version selected>
<1 byte protocol version highest we support>
<remaining bytes discarded>

@ -22,6 +22,7 @@ if(SUBMODULE_CHECK)
check_submodule(pybind11)
check_submodule(sqlite_orm)
check_submodule(oxen-mq)
check_submodule(oxen-encoding)
check_submodule(uvw)
check_submodule(cpr)
check_submodule(ngtcp2)
@ -56,26 +57,27 @@ add_ngtcp2_lib()
# cpr configuration. Ideally we'd just do this via add_subdirectory, but cpr's cmake requires
# 3.15+, and we target lower than that (and this is fairly simple to build).
if(WITH_BOOTSTRAP)
if(NOT BUILD_STATIC_DEPS)
find_package(CURL REQUIRED COMPONENTS HTTP HTTPS SSL)
if(NOT BUILD_STATIC_DEPS)
find_package(CURL REQUIRED COMPONENTS HTTP HTTPS SSL)
# CURL::libcurl wasn't added to FindCURL until cmake 3.12, so add it if necessary
if (CMAKE_VERSION VERSION_LESS 3.12 AND NOT TARGET CURL::libcurl)
add_library(libcurl UNKNOWN IMPORTED GLOBAL)
set_target_properties(libcurl PROPERTIES
IMPORTED_LOCATION ${CURL_LIBRARIES}
INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}")
add_library(CURL_libcurl INTERFACE)
target_link_libraries(CURL_libcurl INTERFACE libcurl)
add_library(CURL::libcurl ALIAS CURL_libcurl)
# CURL::libcurl wasn't added to FindCURL until cmake 3.12, so add it if necessary
if (CMAKE_VERSION VERSION_LESS 3.12 AND NOT TARGET CURL::libcurl)
add_library(libcurl UNKNOWN IMPORTED GLOBAL)
set_target_properties(libcurl PROPERTIES
IMPORTED_LOCATION ${CURL_LIBRARIES}
INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}")
add_library(CURL_libcurl INTERFACE)
target_link_libraries(CURL_libcurl INTERFACE libcurl)
add_library(CURL::libcurl ALIAS CURL_libcurl)
endif()
endif()
endif()
file(GLOB cpr_sources ${conf_depends} cpr/cpr/*.cpp)
file(GLOB cpr_sources ${conf_depends} cpr/cpr/*.cpp)
add_library(cpr STATIC EXCLUDE_FROM_ALL ${cpr_sources})
target_link_libraries(cpr PUBLIC CURL::libcurl)
target_include_directories(cpr PUBLIC cpr/include)
target_compile_definitions(cpr PUBLIC CPR_CURL_NOSIGNAL)
add_library(cpr::cpr ALIAS cpr)
add_library(cpr STATIC EXCLUDE_FROM_ALL ${cpr_sources})
target_link_libraries(cpr PUBLIC CURL::libcurl)
target_include_directories(cpr PUBLIC cpr/include)
target_compile_definitions(cpr PUBLIC CPR_CURL_NOSIGNAL)
add_library(cpr::cpr ALIAS cpr)
endif()

@ -1 +0,0 @@
Subproject commit ac35e705ebf20187b7ff92c29d1411f6a4c8d522

2
external/ngtcp2 vendored

@ -1 +1 @@
Subproject commit 15ba6021ca352e2e60f9b43f4b96d2e97a42f60b
Subproject commit 026b8434ebcbeec48939d1c7671a0a4d5c75202b

@ -0,0 +1 @@
Subproject commit 79193e58fb26624d40cd2e95156f78160f2b9b3e

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save