Compare commits

..
5 Commits
83 changed files with 1464 additions and 4451 deletions

No files matched your search

+9 -9
View File
@@ -7,7 +7,7 @@ env:
UBUNTU_FULL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
UBUNTU_MESON_DEPS: "ninja-build libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-dev python3-setuptools python3-pip libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
UBUNTU_MINIMAL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libfmt-dev libgnutls28-dev libcppunit-dev libmsgpack-dev libargon2-dev"
DEBIAN13_DEPS: "git libtool pkg-config wget cmake libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
DEBIAN12_DEPS: "git libtool pkg-config wget cmake libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
on: [push, pull_request]
@@ -16,7 +16,7 @@ jobs:
name: Ubuntu/GCC Meson build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Dependencies
run: |
@@ -40,7 +40,7 @@ jobs:
matrix:
cxx_standard: [17, 20, 23]
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Minimal Dependencies
run: |
@@ -65,12 +65,12 @@ jobs:
- name: Run Unit tests
run: ctest --output-on-failure --test-dir build
build-debian13:
name: Debian 13/GCC build
build-debian12:
name: Debian 12/GCC build
runs-on: ubuntu-latest
container:
image: debian:13
image: debian:12
volumes:
- .:/workspace
options: --workdir /workspace
@@ -78,12 +78,12 @@ jobs:
DEBIAN_FRONTEND: noninteractive
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Dependencies
run: |
apt-get update
apt-get install -y $DEBIAN13_DEPS
apt-get install -y $DEBIAN12_DEPS
- name: Build llhttp dependency
run: |
@@ -131,7 +131,7 @@ jobs:
matrix:
cxx_standard: [17, 20, 23]
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Homebrew Dependencies
run: brew install msgpack-cxx asio gnutls nettle readline fmt jsoncpp argon2 openssl cppunit
+1 -1
View File
@@ -6,7 +6,7 @@ jobs:
name: Clang Static Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: deps
run: |
sudo apt update && \
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
+9 -9
View File
@@ -16,18 +16,18 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15]
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
# Run locally with: pipx run cibuildwheel --platform linux python
- name: Build wheels
uses: pypa/cibuildwheel@v4.1.1
uses: pypa/cibuildwheel@v3.4.0
with:
package-dir: python
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v6
with:
name: opendht-wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl
@@ -36,7 +36,7 @@ jobs:
name: Build wheels on Windows
runs-on: windows-latest
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2025.07.25"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_DEFAULT_TRIPLET: x64-windows-release
@@ -44,11 +44,11 @@ jobs:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\vcpkg_binary_cache
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Cache vcpkg installed packages
id: cache-vcpkg
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -86,7 +86,7 @@ jobs:
pipx run cibuildwheel python
shell: pwsh
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v6
with:
name: opendht-wheels-win_amd64
path: ./build/wheelhouse/*.whl
@@ -95,7 +95,7 @@ jobs:
runs-on: ubuntu-latest
needs: [build_wheels, build_wheels_windows]
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: opendht-wheels-*
path: dist
+28 -28
View File
@@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -47,7 +47,7 @@ jobs:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDeps
@@ -66,7 +66,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -89,7 +89,7 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
@@ -107,7 +107,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -129,7 +129,7 @@ jobs:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS_LLVM }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDepsLlvm
@@ -148,7 +148,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -171,7 +171,7 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LLVM }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileLlvm
@@ -190,7 +190,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -213,7 +213,7 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DHTNODE }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDhtnode
@@ -231,7 +231,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -254,7 +254,7 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE_DEPS }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDepsAlpine
@@ -273,7 +273,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -296,7 +296,7 @@ jobs:
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileAlpine
@@ -309,7 +309,7 @@ jobs:
name: Build Windows MSI Installer
runs-on: windows-latest
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2025.07.25"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_DEFAULT_TRIPLET: x64-windows
@@ -319,12 +319,12 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up MSVC developer environment
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Install WIX using .net tools
run: |
@@ -334,7 +334,7 @@ jobs:
- name: Cache vcpkg installed packages
id: cache-vcpkg
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -403,7 +403,7 @@ jobs:
}
- name: Upload MSI Artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-msi
path: build/${{ steps.cpack_msi.outputs.msi_path }} # Path to the MSI in the build directory
@@ -413,7 +413,7 @@ jobs:
name: Build Windows dhtnode
runs-on: windows-latest
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2025.07.25"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_DEFAULT_TRIPLET: x64-windows-static-release
@@ -423,16 +423,16 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up MSVC developer environment
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Cache vcpkg static installed packages
id: cache-vcpkg-static
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -483,7 +483,7 @@ jobs:
cmake --build build_static --config Release --parallel $([System.Environment]::ProcessorCount)
- name: Upload Static dhtnode Artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-dhtnode
path: |
@@ -499,23 +499,23 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Download Windows MSI artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: opendht-windows-msi
path: downloaded_artifacts/windows_msi
- name: Download Static dhtnode artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: opendht-windows-dhtnode
path: downloaded_artifacts/static_dhtnode
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
+17 -17
View File
@@ -9,7 +9,7 @@ on:
branches: [ main, master ]
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2026.01.16"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_INSTALLED_DIR: ${{ github.workspace }}\vcpkg_installed
@@ -27,10 +27,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Generate Cache Key
id: generate_cache_key
@@ -41,7 +41,7 @@ jobs:
- name: Cache vcpkg installed packages
id: cache-vcpkg
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -75,10 +75,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Generate Cache Key for Static Libs
id: generate_static_cache_key
@@ -89,7 +89,7 @@ jobs:
- name: Cache vcpkg static installed packages
id: cache-vcpkg-static
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -125,14 +125,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Restore vcpkg installed packages
id: cache-vcpkg-restore
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -181,7 +181,7 @@ jobs:
- name: Upload build artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-${{ matrix.arch }}-cxx${{ matrix.cxx_standard }}-${{ matrix.build_type }}
path: |
@@ -202,14 +202,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Restore vcpkg static installed packages
id: cache-vcpkg-static-restore
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -256,7 +256,7 @@ jobs:
ctest --build-config ${{ matrix.build_type }} --output-on-failure --parallel 4
- name: Upload static build artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-static-${{ matrix.arch }}-cxx${{ matrix.cxx_standard }}-${{ matrix.build_type }}
path: |
@@ -275,7 +275,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Setup MinGW
uses: msys2/setup-msys2@v2
@@ -375,7 +375,7 @@ jobs:
- name: Upload MinGW artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-mingw-${{ matrix.build_type }}
path: |
+4 -3
View File
@@ -67,7 +67,7 @@ sudo dnf install python3-Cython python3-devel redhat-rpm-config
### macOS
```sh
brew install gnutls msgpack-cxx argon2 asio readline jsoncpp fmt llhttp
brew install gnutls msgpack-cxx argon2 asio readline jsoncpp fmt
```
## Build
@@ -81,7 +81,7 @@ git clone https://github.com/savoirfairelinux/opendht.git
# Build and install
cd opendht
mkdir build && cd build
cmake ..
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make -j$(nproc)
sudo make install
```
@@ -100,7 +100,8 @@ cmake -DOPENDHT_PYTHON=ON \
-DOPENDHT_PROXY_SERVER=ON \
-DOPENDHT_PROXY_SERVER_IDENTITY=ON \
-DOPENDHT_PROXY_CLIENT=ON \
-DOPENDHT_PUSH_NOTIFICATIONS=ON ..
-DOPENDHT_PUSH_NOTIFICATIONS=ON \
-DCMAKE_INSTALL_PREFIX=/usr ..
```
#### Common CMake options
+5 -12
View File
@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.21...4.4)
cmake_minimum_required (VERSION 3.21...3.31)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
@@ -9,8 +9,8 @@ if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
endif()
set (opendht_VERSION_MAJOR 4)
set (opendht_VERSION_MINOR 3.0)
set (opendht_VERSION_MAJOR 3)
set (opendht_VERSION_MINOR 7.6)
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
set (PACKAGE_VERSION ${opendht_VERSION})
set (VERSION "${opendht_VERSION}")
@@ -502,11 +502,7 @@ list (APPEND opendht_HEADERS
)
if (OPENDHT_PEER_DISCOVERY)
list (APPEND opendht_SOURCES
src/peer_discovery/mdns.cpp
src/peer_discovery/dns_sd.cpp
src/peer_discovery/mdns_transport.cpp
src/peer_discovery/peer_discovery.cpp)
list (APPEND opendht_SOURCES src/peer_discovery.cpp)
list (APPEND opendht_HEADERS include/opendht/peer_discovery.h)
add_definitions(-DOPENDHT_PEER_DISCOVERY)
endif()
@@ -593,7 +589,6 @@ if (MSVC)
simdutf::simdutf
wsock32
ws2_32
iphlpapi
)
if (OPENDHT_TOOLS)
target_link_libraries(opendht PUBLIC unofficial::readline-win32::readline)
@@ -677,7 +672,7 @@ else()
# System-specific linker pipelines
if (WIN32 AND MINGW) # MSYS2
message(STATUS "Build pipeline: Windows/MSYS2-MinGW")
target_link_libraries(opendht PUBLIC wsock32 ws2_32 iphlpapi)
target_link_libraries(opendht PUBLIC wsock32 ws2_32)
if(OPENDHT_PROXY_OPENSSL)
target_link_libraries(opendht PUBLIC Crypt32)
endif()
@@ -893,8 +888,6 @@ if (BUILD_TESTING)
endif()
if (OPENDHT_PEER_DISCOVERY)
list (APPEND test_FILES
tests/test_mdns.h
tests/test_mdns.cpp
tests/test_peerdiscovery.h
tests/test_peerdiscovery.cpp
)
+2 -2
View File
@@ -600,7 +600,7 @@ dht_runner_put_signed(
void
dht_runner_put_encrypted(dht_runner* r,
const dht_infohash* h,
const dht_pkid* to,
const dht_infohash* to,
const dht_value* v,
dht_done_cb done_cb,
void* cb_user_data,
@@ -608,7 +608,7 @@ dht_runner_put_encrypted(dht_runner* r,
{
auto runner = reinterpret_cast<dht::DhtRunner*>(r);
auto hash = reinterpret_cast<const dht::InfoHash*>(h);
auto toHash = reinterpret_cast<const dht::PkId*>(to);
auto toHash = reinterpret_cast<const dht::InfoHash*>(to);
auto value = reinterpret_cast<const ValueSp*>(v);
runner->putEncrypted(
*hash,
+1 -1
View File
@@ -195,7 +195,7 @@ OPENDHT_C_PUBLIC void dht_runner_put_signed(dht_runner* runner,
bool permanent);
OPENDHT_C_PUBLIC void dht_runner_put_encrypted(dht_runner* runner,
const dht_infohash* hash,
const dht_pkid* to,
const dht_infohash* to,
const dht_value* value,
dht_done_cb done_cb,
void* cb_user_data,
+54 -145
View File
@@ -1,193 +1,102 @@
.TH DHTNODE 1 2026-04-18
.TH DHTNODE 1 2019-06-08
.SH NAME
.B dhtnode
- a simple OpenDHT command line node runner.
.SH SYNOPSIS
.B dhtnode
[\fB\-h\fR]
[\fB\-V\fR]
[\fB\-v\fR [\fB\-l\fR \fIlogfile\fR] [\fB\-L\fR]]
[\fB\-i\fR [\fB\-I\fR \fIfile\fR]]
[\fB\-c\fR \fIfile\fR]
[\fB\-k\fR \fIfile\fR]
[\fB\-m\fR \fIpassword\fR]
[\fB\-i\fR [\fB\-\-save\-identity\fR \fIfile\fR]]
[\fB\-d\fR] [\fB\-s\fR]
[\fB\-D\fR] [\fB\-U\fR] [\fB\-P\fR]
[\fB\-n\fR \fInetwork_id\fR]
[\fB\-p\fR \fIlocal_port\fR]
[\fB\-b\fR \fIbootstrap_host\fR[:\fIport\fR]]
[\fB\-f\fR \fIpath\fR]
[\fB\-S\fR \fIport\fR]
[\fB\-e\fR \fIport\fR]
[\fB\-a\fR \fIaddress\fR]
[\fB\-w\fR \fIfile\fR]
[\fB\-K\fR \fIfile\fR]
[\fB\-M\fR \fIpassword\fR]
[\fB\-C\fR \fIserver\fR]
[\fB\-y\fR \fIendpoint\fR]
[\fB\-z\fR \fIkey\fR]
[\fB\-u\fR \fIbundleid\fR]
[\fB\-\-certificate\fR \fIfile\fR]
[\fB\-\-privkey\fR \fIfile\fR]
[\fB\-\-privkey\-password\fR \fIpassword\fR]
[\fB\-\-proxyserver\fR \fIport\fR]
[\fB\-\-proxyclient\fR \fIserver\fR]
.SH DESCRIPTION
Runs an OpenDHT node, with an interactive CLI (default) or as a daemon (with \fB'-d'\fP or \fB'-s'\fP).
Runs an OpenDHT node, with a CLI (default) or as a daemon (with \fB'-d'\fP or \fB'-s'\fP).
Commands available in the interactive shell are:
.EX
h, help Print this help message.
x, quit Quit the program.
q, quit Quit the program.
log Start/stop printing DHT logs.
Node information:
ll Print basic information and stats about the current node.
ls [key] Print basic information about current search(es).
ld [key] Print basic information about currenty stored values on this
node (or for a specific key).
lr Print the full current routing table of this node.
ls Print basic information about current searches.
ld Print basic information about currenty stored values on this node.
lr Print the full current routing table of this node
Operations on the DHT:
b <ip:port> Ping potential node at given IP address/port.
g <key> Get values at <key>.
l <key> Listen for value changes at <key>.
cl <key> <token> Cancel listen for <token> and <key>.
p <key> <str> Put string value at <key>.
pp <key> <str> Put string value at <key> (persistent version).
cpp <key> <id> Cancel persistent put operation for <key> and
value <id>.
s <key> <str> Put string value at <key>, signed with our
generated private key.
e <key> <dest> <str> Put string value at <key>, encrypted for <dest>
with its public key (if found).
cc Trigger connectivity changed signal.
Operations with the proxy (if compiled with proxy server support):
pst [port] <pushServer> Start the proxy interface on port.
psx [port] Start the proxy SSL interface on port.
psp [port] Stop the proxy interface on port.
Operations with the proxy (if compiled with proxy client support):
stt [server_address] <device_key> Start the proxy client.
stp Stop the proxy client.
rs [token] Resubscribe to opendht.
rp [token] Inject a push notification.
b ip:port Ping potential node at given IP address/port.
g [key] Get values at [key].
l [key] Listen for value changes at [key].
p [key] [str] Put string value at [key].
s [key] [str] Put string value at [key], signed with our generated
private key.
e [key] [dest] [str] Put string value at [key], encrypted for [dest] with
its public key (if found).
.EE
.SH OPTIONS
.TP
\fB\-h\fP, \fB\-\-help\fP
Prints help and exits.
\fB\-h\fP
Prints some help.
.TP
\fB\-V\fP, \fB\-\-version\fP
Prints version information and exits.
\fB\-v\fP
Enable the verbose mode (log to stdout/stderr by default)
.TP
\fB\-v\fP, \fB\-\-verbose\fP
Enable verbose mode (log to stdout/stderr by default).
\fB\-l\fP \fIlog_file\fP
Write log to file instead of stdout/stderr
.TP
\fB\-l\fP \fIlogfile\fP, \fB\-\-logfile\fP \fIlogfile\fP
Write log to file instead of stdout/stderr.
\fB\-L\fP
Write log to syslog instead of stdout/stderr
.TP
\fB\-L\fP, \fB\-\-syslog\fP
Write log to syslog instead of stdout/stderr (enables verbose mode).
\fB\-i\fP
Generate cryptographic identity for the node.
.TP
\fB\-i\fP, \fB\-\-identity\fP
Generate a cryptographic identity (RSA key pair and self-signed certificate)
for the node. Required to perform cryptographic operations (encrypt/sign values)
if no private key file is provided.
.TP
\fB\-I\fP \fIfile\fP, \fB\-\-save\-identity\fP \fIfile\fP
\fB\-\-save\-identity\fP \fIfile\fP
Save generated identity (certificate and private key) to given file prefix.
Files are saved as \fIfile\fP and \fIfile\fP_ca.
.TP
\fB\-c\fP \fIfile\fP, \fB\-\-certificate\fP \fIfile\fP
Load identity certificate from given file. If specified, the certificate is
published on the DHT.
\fB\-\-certificate\fP \fIfile\fP
Load identity certificate from given file.
.TP
\fB\-k\fP \fIfile\fP, \fB\-\-privkey\fP \fIfile\fP
Load identity private key from given file. Required to perform cryptographic
operations if no identity is generated.
\fB\-\-privkey\fP \fIfile\fP
Load identity private key from given file.
.TP
\fB\-m\fP \fIpassword\fP, \fB\-\-privkey\-password\fP \fIpassword\fP
\fB\-\-privkey\-password\fP \fIpassword\fP
Password to use for private key encryption or decryption (optional).
.TP
\fB\-d\fP, \fB\-\-daemonize\fP
Run the program in daemon mode (will fork in the background). When verbose mode
is enabled, logs to syslog by default.
\fB\-d\fP
Run the program in daemon mode (will fork in the background).
.TP
\fB\-s\fP, \fB\-\-service\fP
\fB\-s\fP
Run the program in service mode (non\-forking daemon).
.TP
\fB\-D\fP, \fB\-\-peer\-discovery\fP
\fB\-D\fP
Enables multicast automatic local peer discovery.
.TP
\fB\-U\fP, \fB\-\-no\-rate\-limit\fP
Disable rate limiting (allows unlimited requests and storage).
\fB\-f\fP \fIfile\fP
Specify a file path to persist/load the node state.
.TP
\fB\-P\fP, \fB\-\-public\-stable\fP
Enable public stable mode.
\fB\-n\fP \fInetwork_id\fP
Specify the network id. This let you connect to distinct networks and prevents
the merge of two different networks (available since OpenDHT v0.6.1).
.TP
\fB\-f\fP \fIpath\fP, \fB\-\-persist\fP \fIpath\fP
Specify a file path to persist/load the node state (routing table and values).
\fB\-p\fP \fIlocal_port\fP
Use UDP port \fIlocal_port\fP for the program to bind to.
.TP
\fB\-n\fP \fInetwork_id\fP, \fB\-\-net\fP \fInetwork_id\fP
Specify the network id. This lets you connect to distinct networks and prevents
the merge of two different networks. Defaults to 0 (the main public network).
\fB\-b\fP \fIbootstrap_host\fP[:\fIport\fP]
The program needs to be given a node to connect to the network. You use this
option to provide the ip address of that node.
.TP
\fB\-p\fP \fIlocal_port\fP, \fB\-\-port\fP \fIlocal_port\fP
Use UDP port \fIlocal_port\fP for the program to bind to. If not set, will use
any available port. The default OpenDHT port for public nodes is 4222.
\fB\-\-proxyserver\fP \fIlocal_port\fP
Run a proxy server bound to this DHT node on HTTP port \fIlocal_port\fP
.TP
\fB\-b\fP \fIbootstrap_host\fP[:\fIport\fP], \fB\-\-bootstrap\fP \fIbootstrap_host\fP[:\fIport\fP]
Specify a bootstrap node address to connect to the network.
.TP
\fB\-S\fP \fIlocal_port\fP, \fB\-\-proxyserver\fP \fIlocal_port\fP
Run a REST API proxy server bound to this DHT node on HTTP port \fIlocal_port\fP.
.TP
\fB\-e\fP \fIlocal_port\fP, \fB\-\-proxyserverssl\fP \fIlocal_port\fP
Run a REST API proxy server with SSL bound to this DHT node on HTTPS port \fIlocal_port\fP.
.TP
\fB\-a\fP \fIaddress\fP, \fB\-\-proxy\-addr\fP \fIaddress\fP
Proxy server bind address.
.TP
\fB\-w\fP \fIfile\fP, \fB\-\-proxy\-certificate\fP \fIfile\fP
Load proxy SSL certificate from given file.
.TP
\fB\-K\fP \fIfile\fP, \fB\-\-proxy\-privkey\fP \fIfile\fP
Load proxy SSL private key from given file.
.TP
\fB\-M\fP \fIpassword\fP, \fB\-\-proxy\-privkey\-password\fP \fIpassword\fP
Password for the proxy private key encryption or decryption (optional).
.TP
\fB\-C\fP \fIserver\fP, \fB\-\-proxyclient\fP \fIserver\fP
Run this DHT node in proxy client mode, and connect to \fIserver\fP.
.TP
\fB\-y\fP \fIendpoint\fP, \fB\-\-pushserver\fP \fIendpoint\fP
Push notification server endpoint.
.TP
\fB\-z\fP \fIkey\fP, \fB\-\-devicekey\fP \fIkey\fP
Device key for push notifications.
.TP
\fB\-u\fP \fIbundleid\fP, \fB\-\-bundleid\fP \fIbundleid\fP
Bundle ID for push notifications.
.SH EXAMPLES
Run a node using bootstrap.jami.net as a bootstrap:
.PP
.EX
dhtnode -b bootstrap.jami.net:4222
.EE
.PP
Run a node as a non-forking daemon on port 4222:
.PP
.EX
dhtnode -b bootstrap.jami.net -p 4222 -s
.EE
.PP
Run a proxy server on port 8000:
.PP
.EX
dhtnode -b bootstrap.jami.net:4222 --proxyserver 8000
.EE
.PP
Use a proxy client to connect to a proxy server:
.PP
.EX
dhtnode --proxyclient dhtproxy.jami.net:80
.EE
\fB\-\-proxyclient\fP \fIserver\fP
Run this DHT node in proxy client mode, and connect to \fIserver\fP
.SH AUTHORS
.TP
Program written by
+2
View File
@@ -187,6 +187,8 @@ using ShutdownCallback = std::function<void()>;
using IdentityAnnouncedCb = std::function<void(bool)>;
using PublicAddressChangedCb = std::function<void(std::vector<SockAddr>)>;
using CertificateStoreQueryLegacy
= std::function<std::vector<std::shared_ptr<crypto::Certificate>>(const InfoHash& pk_id)>;
using CertificateStoreQuery = std::function<std::vector<std::shared_ptr<crypto::Certificate>>(const PkId& pk_id)>;
using DoneCallback = std::function<void(bool success, const std::vector<std::shared_ptr<Node>>& nodes)>;
+2 -7
View File
@@ -267,14 +267,9 @@ public:
/**
* Sign this revocation list using provided key and certificate.
* Validity_period sets the duration until next update (default to no next update).
* If `number` is non-zero, it is stored verbatim (big-endian) as the CRL Number
* extension. Otherwise the number is initialized randomly on the first signature
* and incremented on subsequent ones.
*/
void sign(const PrivateKey&, const Certificate&, duration validity_period = {}, uint64_t number = 0);
void sign(const Identity& id, duration validity_period = {}, uint64_t number = 0) {
sign(*id.first, *id.second, validity_period, number);
}
void sign(const PrivateKey&, const Certificate&, duration validity_period = {});
void sign(const Identity& id) { sign(*id.first, *id.second); }
bool isSignedBy(const Certificate& issuer) const;
-6
View File
@@ -159,13 +159,7 @@ public:
{}
static Value::Filter getFilter() { return EncryptedValue::getFilter(); }
virtual void unpackValue(const dht::Value& v) override
{
BaseClass::unpackValue(v);
id = v.id;
}
dht::Value::Id id {0}; /// Value ID of the trust request, used for deserialization only.
std::string service;
std::string conversationId;
Blob payload;
+2 -2
View File
@@ -425,7 +425,7 @@ private:
p.pack(clientId);
}
if (sessionCtx) {
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
p.pack("sid");
p.pack(sessionCtx->sessionId);
}
@@ -478,7 +478,7 @@ private:
p.pack("exp");
p.pack(to_time_t(expiration));
if (sessionCtx) {
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
p.pack("sid");
p.pack(sessionCtx->sessionId);
}
+24 -10
View File
@@ -61,7 +61,8 @@ public:
std::unique_ptr<net::DatagramSocket> sock;
std::shared_ptr<PeerDiscovery> peerDiscovery {};
StatusCallback statusChangedCallback {};
CertificateStoreQuery certificateStore {};
CertificateStoreQueryLegacy certificateStore {};
CertificateStoreQuery certificateStorePkId {};
IdentityAnnouncedCb identityAnnouncedCb {};
PublicAddressChangedCb publicAddressChangedCb {};
std::unique_ptr<std::mt19937_64> rng {};
@@ -285,6 +286,21 @@ public:
}
void putSigned(const std::string& key, Value&& value, DoneCallbackSimple cb = {}, bool permanent = false);
void putEncrypted(
InfoHash hash, InfoHash to, std::shared_ptr<Value> value, DoneCallback cb = {}, bool permanent = false);
void putEncrypted(
InfoHash hash, InfoHash to, std::shared_ptr<Value> value, DoneCallbackSimple cb, bool permanent = false)
{
putEncrypted(hash, to, value, bindDoneCb(cb), permanent);
}
void putEncrypted(InfoHash hash, InfoHash to, Value&& value, DoneCallback cb = {}, bool permanent = false);
void putEncrypted(InfoHash hash, InfoHash to, Value&& value, DoneCallbackSimple cb, bool permanent = false)
{
putEncrypted(hash, to, std::forward<Value>(value), bindDoneCb(cb), permanent);
}
void putEncrypted(const std::string& key, InfoHash to, Value&& value, DoneCallback cb = {}, bool permanent = false);
void putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
std::shared_ptr<Value> value,
@@ -299,11 +315,13 @@ public:
putEncrypted(hash, to, value, bindDoneCb(cb), permanent);
}
[[deprecated("Use the shared_ptr version instead")]]
void putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
Value&& value,
DoneCallback cb = {},
bool permanent = false);
[[deprecated("Use the shared_ptr version instead")]]
void putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
Value&& value,
@@ -320,14 +338,6 @@ public:
{
putEncrypted(hash, to, value, bindDoneCb(cb), permanent);
}
void putEncrypted(InfoHash hash, const PkId& to, Value&& value, DoneCallback cb = {}, bool permanent = false)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(value)), std::move(cb), permanent);
}
void putEncrypted(InfoHash hash, const PkId& to, Value&& value, DoneCallbackSimple cb, bool permanent = false)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(value)), bindDoneCb(cb), permanent);
}
/**
* Insert known nodes to the routing table, without necessarly ping them.
@@ -440,9 +450,13 @@ public:
void getPublicAddress(std::function<void(std::vector<SockAddr>&&)>, sa_family_t af = AF_UNSPEC);
// securedht methods
[[deprecated("Use PkId version")]] void findCertificate(
InfoHash hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
void findCertificate(PkId hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
void registerCertificate(const std::shared_ptr<crypto::Certificate>& cert);
void setLocalCertificateStore(CertificateStoreQueryLegacy&& query_method);
void setLocalCertificateStore(CertificateStoreQuery&& query_method);
/**
@@ -485,7 +499,7 @@ public:
*/
time_point loop()
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return loop_();
}
+14 -8
View File
@@ -51,11 +51,11 @@ using ConnectHandlerCb = std::function<void(const asio::error_code& ec, const as
using ssl_socket_t = restinio::impl::tls_socket_t;
using socket_t = asio::ip::tcp::socket;
struct OPENDHT_PUBLIC Url
class OPENDHT_PUBLIC Url
{
public:
Url() = default;
Url(std::string url);
Url(std::string_view url);
std::string url;
std::string protocol {"http"};
std::string_view user;
@@ -149,7 +149,7 @@ public:
using ResolverCb
= std::function<void(const asio::error_code& ec, const std::vector<asio::ip::tcp::endpoint>& endpoints)>;
Resolver(asio::io_context& ctx, std::string url, std::shared_ptr<log::Logger> logger = {});
Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
Resolver(asio::io_context& ctx,
std::string_view host,
std::string_view service,
@@ -161,6 +161,10 @@ public:
std::vector<asio::ip::tcp::endpoint> endpoints,
const bool ssl = false,
std::shared_ptr<log::Logger> logger = {});
Resolver(asio::io_context& ctx,
const std::string& url,
std::vector<asio::ip::tcp::endpoint> endpoints,
std::shared_ptr<log::Logger> logger = {});
~Resolver();
@@ -212,19 +216,19 @@ public:
// resolves implicitly
Request(asio::io_context& ctx,
std::string url,
const std::string& url,
const Json::Value& json,
OnJsonCb jsoncb,
std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, std::string url, OnJsonCb jsoncb, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, std::string url, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx,
std::string_view host,
std::string_view service,
const bool ssl = false,
std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, std::string url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
// user defined resolver
Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, sa_family_t family = AF_UNSPEC);
@@ -340,6 +344,8 @@ private:
dht::crypto::Identity client_identity_;
std::shared_ptr<dht::crypto::Certificate> server_ca_;
std::string service_;
std::string host_;
unsigned int id_;
static std::atomic_uint ids_;
+1 -1
View File
@@ -269,7 +269,7 @@ public:
}
private:
alignas(alignof(uint32_t)) T data_;
T data_;
static constexpr T fromBytes(const uint8_t*, size_t);
static constexpr T fromString(std::string_view);
};
+2 -2
View File
@@ -131,7 +131,7 @@ struct OPENDHT_PUBLIC Logger
std::shared_ptr<Logger> createChild(std::string tag)
{
std::lock_guard lock {children_mutex_};
std::lock_guard<std::mutex> lock {children_mutex_};
auto child = std::make_shared<Logger>(*this, std::move(tag));
children_.push_back(child);
return child;
@@ -139,7 +139,7 @@ struct OPENDHT_PUBLIC Logger
void setFilter(std::string_view filter)
{
std::lock_guard lock {children_mutex_};
std::lock_guard<std::mutex> lock {children_mutex_};
enable_ = filter.empty() or tag_.find(filter) != std::string_view::npos;
for (auto it = children_.begin(); it != children_.end();) {
if (auto c = it->lock()) {
+2 -7
View File
@@ -489,12 +489,8 @@ private:
static constexpr size_t MTU {1280};
static constexpr size_t MAX_PACKET_VALUE_SIZE {600};
static constexpr size_t MAX_MESSAGE_VALUE_SIZE {56 * 1024};
static constexpr size_t RX_MAX_EARLY_BYTES {MAX_MESSAGE_VALUE_SIZE + MAX_VALUE_SIZE + 32};
static constexpr size_t RX_MAX_EARLY_PARTS {RX_MAX_EARLY_BYTES / MTU + 2};
void process(std::unique_ptr<ParsedMessage>&&, const SockAddr& from);
bool isExpectedReply(const ParsedMessage&, const SockAddr& from);
bool tryCompleteMessage(PartialMessage&, const SockAddr& from);
bool rateLimit(const SockAddr& addr);
@@ -531,8 +527,7 @@ private:
{
return packValueHeader(buf, values.begin(), values.end());
}
using PartialMessageKey = std::pair<SockAddr, Tid>;
void maintainRxBuffer(const PartialMessageKey& key);
void maintainRxBuffer(Tid tid);
/*************
* Answers *
@@ -578,7 +573,7 @@ private:
// requests handling
std::map<Tid, Sp<Request>> requests {};
std::map<PartialMessageKey, PartialMessage> partial_messages;
std::map<Tid, PartialMessage> partial_messages;
MessageStats in_stats {}, out_stats {};
std::set<SockAddr> blacklist {};
+6 -6
View File
@@ -58,7 +58,7 @@ public:
inline void setOnReceive(OnReceive&& cb)
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
rx_callback = std::move(cb);
}
@@ -67,12 +67,12 @@ public:
SockAddr getBound(sa_family_t family = AF_UNSPEC) const
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return getBoundRef(family);
}
in_port_t getPort(sa_family_t family = AF_UNSPEC) const
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return getBoundRef(family).getPort();
}
@@ -102,7 +102,7 @@ protected:
inline void onReceived(PacketList&& packets)
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
if (rx_callback) {
auto r = rx_callback(std::move(packets));
if (not r.empty() and toRecycle_.size() < RX_QUEUE_MAX_SIZE)
@@ -134,12 +134,12 @@ public:
bool hasIPv4() const override
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return s4 != -1;
}
bool hasIPv6() const override
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return s6 != -1;
}
+52 -13
View File
@@ -3,13 +3,12 @@
#pragma once
#include "def.h"
#include "sockaddr.h"
#include "infohash.h"
#include "logger.h"
#include "sockaddr.h"
#include "utils.h"
#include <functional>
#include <memory>
#include <asio/steady_timer.hpp>
#include <thread>
namespace asio {
@@ -21,26 +20,66 @@ namespace dht {
class OPENDHT_PUBLIC PeerDiscovery
{
public:
using PeerDiscoveredCallback = std::function<void(const InfoHash&, SockAddr&&)>;
static constexpr in_port_t DEFAULT_PORT = 8888;
using ServiceDiscoveredCallback = std::function<void(msgpack::object&&, SockAddr&&)>;
PeerDiscovery(std::shared_ptr<asio::io_context> ioContext = {}, std::shared_ptr<Logger> logger = {});
PeerDiscovery(in_port_t port = DEFAULT_PORT,
std::shared_ptr<asio::io_context> ioContext = {},
std::shared_ptr<Logger> logger = {});
~PeerDiscovery();
void startDiscovery(NetId network, PeerDiscoveredCallback callback);
void startPublish(const InfoHash& nodeId, NetId network, in_port_t port);
/**
* startDiscovery - Keep Listening data from the sender until node is joinned
* or stop is called
*/
void startDiscovery(std::string_view type, ServiceDiscoveredCallback callback);
bool stopDiscovery();
bool stopPublish();
template<typename T>
void startDiscovery(std::string_view type, std::function<void(T&&, SockAddr&&)> cb)
{
startDiscovery(type, [cb](msgpack::object&& ob, SockAddr&& addr) { cb(ob.as<T>(), std::move(addr)); });
}
/**
* startPublish - Keeping sending data until node is joinned or stop is called
*/
void startPublish(std::string_view type, const msgpack::sbuffer& pack_buf);
void startPublish(sa_family_t domain, std::string_view type, const msgpack::sbuffer& pack_buf);
template<typename T>
void startPublish(std::string_view type, const T& object)
{
msgpack::sbuffer buf;
msgpack::pack(buf, object);
startPublish(type, buf);
}
/**
* Thread Stopper
*/
void stop();
/**
* Remove possible callBack to discovery
*/
bool stopDiscovery(std::string_view type);
/**
* Remove different serivce message to send
*/
bool stopPublish(std::string_view type);
bool stopPublish(sa_family_t domain, std::string_view type);
void connectivityChanged();
void stopConnectivityChanged();
private:
class Impl;
std::shared_ptr<Impl> impl_;
class DomainPeerDiscovery;
std::unique_ptr<DomainPeerDiscovery> peerDiscovery4_;
std::unique_ptr<DomainPeerDiscovery> peerDiscovery6_;
std::shared_ptr<asio::io_context> ioContext_;
std::thread ioRunner_;
std::thread ioRunnner_;
};
} // namespace dht
} // namespace dht
+25 -2
View File
@@ -113,6 +113,14 @@ public:
* and put it in the DHT.
* The operation will be immediate if the recipient' public key is known (otherwise it will be retrived first).
*/
void putEncrypted(
const InfoHash& hash, const InfoHash& to, Sp<Value> val, DoneCallback callback, bool permanent = false);
[[deprecated("Use the shared_ptr version instead")]]
void putEncrypted(const InfoHash& hash, const InfoHash& to, Value&& v, DoneCallback callback, bool permanent = false)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(v)), callback, permanent);
}
void putEncrypted(const InfoHash& hash,
const crypto::PublicKey& to,
Sp<Value> val,
@@ -137,11 +145,19 @@ public:
Value decrypt(const Value& v);
[[deprecated("Use PkId version")]] void findCertificate(
const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb);
[[deprecated("Use PkId version")]] void findPublicKey(const InfoHash& node,
const std::function<void(const Sp<crypto::PublicKey>)>& cb);
void findCertificate(const PkId& id, const std::function<void(const Sp<crypto::Certificate>)>& cb);
void findPublicKey(const PkId& id, const std::function<void(const Sp<crypto::PublicKey>)>& cb);
void registerCertificate(const Sp<crypto::Certificate>& cert);
[[deprecated("Use PkId version")]] Sp<crypto::Certificate> getCertificate(const InfoHash& node) const;
[[deprecated("Use PkId version")]] Sp<crypto::PublicKey> getPublicKey(const InfoHash& node) const;
Sp<crypto::Certificate> getCertificate(const PkId& node) const;
Sp<crypto::PublicKey> getPublicKey(const PkId& node) const;
@@ -150,6 +166,10 @@ public:
* The search key used is the public key ID, so there may be multiple certificates retured, signed with
* the same private key.
*/
void setLocalCertificateStore(CertificateStoreQueryLegacy&& query_method)
{
localQueryMethodLegacy_ = std::move(query_method);
}
void setLocalCertificateStore(CertificateStoreQuery&& query_method) { localQueryMethod_ = std::move(query_method); }
void setOnPublicAddressChanged(PublicAddressChangedCb cb) override { dht_->setOnPublicAddressChanged(cb); }
@@ -327,10 +347,13 @@ private:
// method to query the local certificate store
CertificateStoreQuery localQueryMethod_ {};
CertificateStoreQueryLegacy localQueryMethodLegacy_ {};
// our certificate cache
std::map<PkId, Sp<crypto::Certificate>> nodesCertificates_ {};
std::map<PkId, Sp<crypto::PublicKey>> nodesPubKeys_ {};
std::map<InfoHash, Sp<crypto::Certificate>> nodesCertificates_ {};
std::map<InfoHash, Sp<crypto::PublicKey>> nodesPubKeys_ {};
std::map<PkId, Sp<crypto::Certificate>> nodesCertificatesLong_ {};
std::map<PkId, Sp<crypto::PublicKey>> nodesPubKeysLong_ {};
std::atomic_bool forward_all_ {false};
bool enableCache_ {false};
+5 -9
View File
@@ -10,9 +10,7 @@
#include <future>
#include <functional>
#if __cplusplus < 202002L
#include <ciso646>
#endif
#include <ciso646> // fix windows compiler bug
namespace dht {
@@ -57,12 +55,10 @@ public:
private:
std::mutex lock_;
std::condition_variable cv_ {};
std::condition_variable stopCv_ {};
std::queue<std::function<void()>> tasks_ {};
std::vector<std::unique_ptr<std::thread>> threads_;
unsigned readyThreads_ {0};
bool running_ {true};
bool stopping_ {false};
unsigned minThreads_;
const unsigned maxThreads_;
@@ -108,7 +104,7 @@ public:
void run(std::function<void()>&& task)
{
std::lock_guard lock(state_->mtx);
std::lock_guard<std::mutex> lock(state_->mtx);
if (state_->shutdown_)
return;
state_->pendingTasks++;
@@ -129,7 +125,7 @@ private:
void destroy(bool wait = true)
{
std::unique_lock lock(mtx);
std::unique_lock<std::mutex> lock(mtx);
if (destroyed)
return;
if (wait) {
@@ -145,7 +141,7 @@ private:
void run(const std::function<void()>& task)
{
{
std::lock_guard lock(mtx);
std::lock_guard<std::mutex> lock(mtx);
pendingTasks--;
ongoingTasks++;
}
@@ -153,7 +149,7 @@ private:
return;
task();
{
std::lock_guard lock(mtx);
std::lock_guard<std::mutex> lock(mtx);
ongoingTasks--;
cv.notify_all();
}
-8
View File
@@ -85,14 +85,6 @@ using system_clock = std::chrono::system_clock;
using time_point = clock::time_point;
using duration = clock::duration;
OPENDHT_PUBLIC time_point from_system_time(system_clock::time_point t,
time_point steady_now,
system_clock::time_point system_now);
OPENDHT_PUBLIC time_point from_system_time(system_clock::time_point t);
OPENDHT_PUBLIC system_clock::time_point to_system_time(time_point t,
time_point steady_now,
system_clock::time_point system_now);
OPENDHT_PUBLIC system_clock::time_point to_system_time(time_point t);
OPENDHT_PUBLIC time_point from_time_t(std::time_t t);
OPENDHT_PUBLIC std::time_t to_time_t(time_point t);
+1 -14
View File
@@ -31,7 +31,6 @@ using namespace std::literals;
static constexpr auto VALUE_KEY_ID("id");
static const std::string VALUE_KEY_DAT("dat");
static const std::string VALUE_KEY_PRIO("p");
static const std::string VALUE_KEY_PUSHTYPE("pt");
static const std::string VALUE_KEY_SIGNATURE("sig");
static const std::string VALUE_KEY_SEQ("seq");
@@ -452,7 +451,6 @@ struct OPENDHT_PUBLIC Value
, signature(std::move(o.signature))
, cypher(std::move(o.cypher))
, priority(o.priority)
, pushType(std::move(o.pushType))
{}
template<typename Type>
@@ -576,7 +574,7 @@ struct OPENDHT_PUBLIC Value
template<typename Packer>
void msgpack_pack(Packer& pk) const
{
pk.pack_map(2 + (priority ? 1 : 0) + (!pushType.empty() ? 1 : 0));
pk.pack_map(2 + (priority ? 1 : 0));
pk.pack(VALUE_KEY_ID);
pk.pack(id);
pk.pack(VALUE_KEY_DAT);
@@ -585,10 +583,6 @@ struct OPENDHT_PUBLIC Value
pk.pack(VALUE_KEY_PRIO);
pk.pack(priority);
}
if (!pushType.empty()) {
pk.pack(VALUE_KEY_PUSHTYPE);
pk.pack(pushType);
}
}
template<typename Packer>
@@ -678,13 +672,6 @@ struct OPENDHT_PUBLIC Value
*/
unsigned priority {0};
/**
* Push notification type — set by the sender so that the proxy server
* can include it in the FCM/APNs payload without decrypting the value.
* Example: "audioCall", "videoCall"
*/
std::string pushType {};
inline bool isSignatureChecked() const { return signatureChecked; }
inline bool isDecrypted() const { return decrypted; }
bool checkSignature();
+8 -41
View File
@@ -5,7 +5,7 @@ project(
'cython',
license: 'MIT',
license_files: 'LICENSE',
version: '4.3.0',
version: '3.7.6',
meson_version: '>=1.1.0',
default_options: ['cpp_std=c++20', 'warning_level=3'],
)
@@ -15,23 +15,10 @@ nettle = dependency('nettle')
msgpack = dependency('msgpack-cxx', required: false)
argon2 = dependency('libargon2')
fmt = dependency('fmt')
proxy_enabled = get_option('proxy_client').enabled() or get_option('proxy_server').enabled()
openssl = dependency('openssl', required: proxy_enabled)
jsoncpp = dependency('jsoncpp', required: proxy_enabled)
llhttp = dependency('llhttp', 'libllhttp', required: proxy_enabled)
simdutf = dependency('simdutf', required: proxy_enabled)
# restinio is a header-only library that may not ship a pkg-config file
restinio_dep = dependency('restinio', required: false)
if not restinio_dep.found()
cpp = meson.get_compiler('cpp')
if cpp.has_header('restinio/all.hpp', required: proxy_enabled)
restinio_dep = declare_dependency()
endif
endif
openssl = dependency('openssl', required: get_option('proxy_client'))
jsoncpp = dependency('jsoncpp', required: get_option('proxy_client'))
llhttp = dependency('llhttp', 'libllhttp', required: get_option('proxy_client'))
simdutf = dependency('simdutf', required: get_option('proxy_client'))
io_uring = host_machine.system() == 'linux' ? dependency('liburing', required: false) : disabler()
deps = [
@@ -46,10 +33,6 @@ deps = [
llhttp,
]
if restinio_dep.found()
deps += restinio_dep
endif
if host_machine.system() == 'linux' and io_uring.found()
deps += io_uring
endif
@@ -96,7 +79,7 @@ if get_option('indexation').enabled()
opendht_src += 'src/indexation/pht.cpp'
add_project_arguments('-DOPENDHT_INDEXATION', language: 'cpp')
endif
if jsoncpp.found() and simdutf.found()
if jsoncpp.found()
opendht_src += ['src/base64.cpp']
add_project_arguments('-DOPENDHT_JSONCPP', language: 'cpp')
conf_data.set('jsoncpp_lib', ', jsoncpp')
@@ -123,21 +106,13 @@ if get_option('push_notifications').enabled()
add_project_arguments('-DOPENDHT_PUSH_NOTIFICATIONS', language: 'cpp')
endif
if get_option('peer_discovery').enabled()
opendht_src += [
'src/peer_discovery/mdns.cpp',
'src/peer_discovery/dns_sd.cpp',
'src/peer_discovery/mdns_transport.cpp',
'src/peer_discovery/peer_discovery.cpp',
]
opendht_src += 'src/peer_discovery.cpp'
add_project_arguments('-DOPENDHT_PEER_DISCOVERY', language: 'cpp')
if host_machine.system() == 'windows'
deps += meson.get_compiler('cpp').find_library('iphlpapi')
endif
endif
opendht = library(
'opendht',
opendht_src,
include_directories: [opendht_inc, opendht_interface_inc],
include_directories: opendht_inc,
dependencies: deps,
cpp_args: ['-DOPENDHT_BUILD', '-Dopendht_EXPORTS'],
install: true,
@@ -335,14 +310,6 @@ if get_option('tests').enabled()
endif
if get_option('peer_discovery').enabled()
test_mdns = executable(
'test_mdns',
'tests/test_mdns.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Mdns', test_mdns)
test_peerdiscovery = executable(
'test_peerdiscovery',
'tests/test_peerdiscovery.cpp',
+6 -1
View File
@@ -820,8 +820,13 @@ cdef class DhtRunner(_WithID):
key._infohash, (<PkId>to)._pkid, val._value,
cpp.bindDoneCb(done_callback, <void*>cb_obj), permanent
)
elif isinstance(to, InfoHash):
self.thisptr.get().putEncrypted(
key._infohash, (<InfoHash>to)._infohash, val._value,
cpp.bindDoneCb(done_callback, <void*>cb_obj), permanent
)
else:
raise TypeError("'to' must be PublicKey or PkId")
raise TypeError("'to' must be PublicKey, PkId, or InfoHash")
except:
ref.Py_DECREF(cb_obj)
raise
+1
View File
@@ -282,6 +282,7 @@ cdef extern from "opendht/dhtrunner.h" namespace "dht":
void get(InfoHash key, GetCallback get_cb, DoneCallback done_cb, nullptr_t f, Where w)
void put(InfoHash key, shared_ptr[Value] val, DoneCallback done_cb, time_point created, bool permanent)
void putSigned(InfoHash key, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void putEncrypted(InfoHash key, InfoHash to, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void putEncrypted(InfoHash key, PkId to, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void putEncrypted(InfoHash key, shared_ptr[PublicKey] to, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void cancelPut(InfoHash key, shared_ptr[Value] val)
+5 -5
View File
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
[tool.cibuildwheel]
build = [
'cp3{9,10,11,12,13,14}-manylinux_x86_64',
'cp3{10,11,12,13,14}-manylinux_aarch64',
'cp3{11,12,13,14}-macosx_x86_64',
'cp3{11,12,13,14}-macosx_arm64',
'cp3{11,12,13,14}-win_amd64',
'cp3{9,10,11,12,13}-manylinux_x86_64',
'cp3{10,11,12,13}-manylinux_aarch64',
'cp3{11,12,13}-macosx_x86_64',
'cp3{11,12,13}-macosx_arm64',
'cp3{11,12,13}-win_amd64',
]
[tool.cibuildwheel.linux]
+1 -1
View File
@@ -226,7 +226,7 @@ impl DhtRunner {
pub fn put_encrypted<'a>(
&mut self,
h: &InfoHash,
to: &PkId,
to: &InfoHash,
v: Box<Value>,
done_cb: &'a mut (dyn FnMut(bool)),
permanent: bool,
+1 -1
View File
@@ -190,7 +190,7 @@ extern {
cb_user_data: *mut c_void,
permanent: bool);
pub fn dht_runner_put_encrypted(dht: *mut DhtRunner, h: *const InfoHash,
to: *const PkId, v: *const Value,
to: *const InfoHash, v: *const Value,
done_cb: extern fn(bool, *mut c_void),
cb_user_data: *mut c_void,
permanent: bool);
-15
View File
@@ -36,19 +36,4 @@ base64_decode(std::string_view str)
return buffer;
}
std::vector<unsigned char>
base64_default_or_url_decode(std::string_view str)
{
std::vector<unsigned char> buffer(simdutf::maximal_binary_length_from_base64(str.data(), str.size()));
simdutf::result r = simdutf::base64_to_binary(str.data(),
str.size(),
(char*) buffer.data(),
simdutf::base64_options::base64_default_or_url);
if (r.error) {
throw std::invalid_argument(concat("Invalid base64 input: "sv, simdutf::error_to_string(r.error)));
} else {
buffer.resize(r.count);
}
return buffer;
}
} // namespace dht
+1 -8
View File
@@ -21,12 +21,5 @@ std::string base64_encode(const std::vector<unsigned char>& str);
* @return a base64-decoded buffer
*/
std::vector<unsigned char> base64_decode(std::string_view str);
/**
* Decode a buffer in base64 or url style base64 (including '-' '_' ).
*
* @param str the input buffer
* @return a base64-decoded buffer
*/
std::vector<unsigned char> base64_default_or_url_decode(std::string_view str);
} // namespace dht
} // namespace dht
+52 -85
View File
@@ -11,7 +11,6 @@ extern "C" {
#include <nettle/gcm.h>
#include <nettle/aes.h>
#include <nettle/hmac.h>
#include <nettle/version.h>
#include <gnutls/crypto.h>
#include <argon2.h>
@@ -99,46 +98,6 @@ private:
#define GCM_DIGEST_SIZE GCM_BLOCK_SIZE
#endif
static void
gcmAes128Digest(gcm_aes128_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
gcm_aes128_digest(ctx, digest);
#else
gcm_aes128_digest(ctx, GCM_DIGEST_SIZE, digest);
#endif
}
static void
gcmAes192Digest(gcm_aes192_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
gcm_aes192_digest(ctx, digest);
#else
gcm_aes192_digest(ctx, GCM_DIGEST_SIZE, digest);
#endif
}
static void
gcmAes256Digest(gcm_aes256_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
gcm_aes256_digest(ctx, digest);
#else
gcm_aes256_digest(ctx, GCM_DIGEST_SIZE, digest);
#endif
}
static void
hmacSha256Digest(hmac_sha256_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
hmac_sha256_digest(ctx, digest);
#else
hmac_sha256_digest(ctx, SHA256_DIGEST_SIZE, digest);
#endif
}
Blob
aesEncrypt(const uint8_t* data, size_t data_length, const Blob& key)
{
@@ -156,19 +115,19 @@ aesEncrypt(const uint8_t* data, size_t data_length, const Blob& key)
gcm_aes128_set_key(&aes, key.data());
gcm_aes128_set_iv(&aes, GCM_IV_SIZE, ret.data());
gcm_aes128_encrypt(&aes, data_length, ret.data() + GCM_IV_SIZE, data);
gcmAes128Digest(&aes, ret.data() + GCM_IV_SIZE + data_length);
gcm_aes128_digest(&aes, GCM_DIGEST_SIZE, ret.data() + GCM_IV_SIZE + data_length);
} else if (key.size() == AES_LENGTHS[1]) {
struct gcm_aes192_ctx aes;
gcm_aes192_set_key(&aes, key.data());
gcm_aes192_set_iv(&aes, GCM_IV_SIZE, ret.data());
gcm_aes192_encrypt(&aes, data_length, ret.data() + GCM_IV_SIZE, data);
gcmAes192Digest(&aes, ret.data() + GCM_IV_SIZE + data_length);
gcm_aes192_digest(&aes, GCM_DIGEST_SIZE, ret.data() + GCM_IV_SIZE + data_length);
} else if (key.size() == AES_LENGTHS[2]) {
struct gcm_aes256_ctx aes;
gcm_aes256_set_key(&aes, key.data());
gcm_aes256_set_iv(&aes, GCM_IV_SIZE, ret.data());
gcm_aes256_encrypt(&aes, data_length, ret.data() + GCM_IV_SIZE, data);
gcmAes256Digest(&aes, ret.data() + GCM_IV_SIZE + data_length);
gcm_aes256_digest(&aes, GCM_DIGEST_SIZE, ret.data() + GCM_IV_SIZE + data_length);
}
return ret;
@@ -201,19 +160,19 @@ aesDecrypt(const uint8_t* data, size_t data_length, const Blob& key)
gcm_aes128_set_key(&aes, key.data());
gcm_aes128_set_iv(&aes, GCM_IV_SIZE, data);
gcm_aes128_decrypt(&aes, data_sz, ret.data(), data + GCM_IV_SIZE);
gcmAes128Digest(&aes, digest.data());
gcm_aes128_digest(&aes, GCM_DIGEST_SIZE, digest.data());
} else if (key.size() == AES_LENGTHS[1]) {
struct gcm_aes192_ctx aes;
gcm_aes192_set_key(&aes, key.data());
gcm_aes192_set_iv(&aes, GCM_IV_SIZE, data);
gcm_aes192_decrypt(&aes, data_sz, ret.data(), data + GCM_IV_SIZE);
gcmAes192Digest(&aes, digest.data());
gcm_aes192_digest(&aes, GCM_DIGEST_SIZE, digest.data());
} else if (key.size() == AES_LENGTHS[2]) {
struct gcm_aes256_ctx aes;
gcm_aes256_set_key(&aes, key.data());
gcm_aes256_set_iv(&aes, GCM_IV_SIZE, data);
gcm_aes256_decrypt(&aes, data_sz, ret.data(), data + GCM_IV_SIZE);
gcmAes256Digest(&aes, digest.data());
gcm_aes256_digest(&aes, GCM_DIGEST_SIZE, digest.data());
}
if (not std::equal(digest.begin(), digest.end(), data + data_length - GCM_DIGEST_SIZE)) {
@@ -487,7 +446,7 @@ PrivateKey::getPublicKey() const
const std::shared_ptr<PublicKey>&
PrivateKey::getSharedPublicKey() const
{
std::lock_guard lock(publicKeyMutex_);
std::lock_guard<std::mutex> lock(publicKeyMutex_);
if (not publicKey_) {
auto pk = std::make_shared<PublicKey>();
if (auto err = gnutls_pubkey_import_privkey(pk->pk, key, GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN, 0))
@@ -571,12 +530,6 @@ PublicKey::toString() const
}
if (err != GNUTLS_E_SUCCESS)
throw CryptoException(std::string("Could not print public key: ") + gnutls_strerror(err));
// GnuTLS reports the PEM size including the terminating null byte, which
// does not belong to the text and would end up embedded in whatever the
// string is copied into, such as the "owner" field of a serialized value.
ret.resize(sz);
if (not ret.empty() and ret.back() == '\0')
ret.pop_back();
return ret;
}
@@ -1006,7 +959,7 @@ Certificate::getPublicKey() const
const std::shared_ptr<PublicKey>&
Certificate::getSharedPublicKey() const
{
std::lock_guard lock(publicKeyMutex_);
std::lock_guard<std::mutex> lock(publicKeyMutex_);
if (not publicKey_) {
auto pk = std::make_shared<PublicKey>();
if (auto err = gnutls_pubkey_import_x509(pk->pk, cert, 0))
@@ -1402,11 +1355,8 @@ setRandomSerial(gnutls_x509_crt_t cert)
{
std::random_device rdev;
std::uniform_int_distribution<int64_t> dist {1};
const auto cert_serial = dist(rdev);
std::array<uint8_t, sizeof(cert_serial)> packed_serial;
for (size_t i = 0; i < packed_serial.size(); i++)
packed_serial[i] = cert_serial >> (8 * (packed_serial.size() - 1 - i));
gnutls_x509_crt_set_serial(cert, packed_serial.data(), packed_serial.size());
int64_t cert_serial = dist(rdev);
gnutls_x509_crt_set_serial(cert, &cert_serial, sizeof(cert_serial));
}
Certificate
@@ -1882,8 +1832,34 @@ RevocationList::getUpdateTime() const
return std::chrono::system_clock::from_time_t(t);
}
enum class Endian : uint32_t { LITTLE = 0, BIG = 1 };
template<typename T>
T
endian(T w, Endian endian = Endian::BIG)
{
// this gets optimized out into if (endian == host_endian) return w;
union {
uint64_t quad;
uint32_t islittle;
} t;
t.quad = 1;
if (t.islittle ^ (uint32_t) endian)
return w;
T r = 0;
// decent compilers will unroll this (gcc)
// or even convert straight into single bswap (clang)
for (size_t i = 0; i < sizeof(r); i++) {
r <<= 8;
r |= w & 0xff;
w >>= 8;
}
return r;
}
void
RevocationList::sign(const PrivateKey& key, const Certificate& ca, duration validity, uint64_t crlNumber)
RevocationList::sign(const PrivateKey& key, const Certificate& ca, duration validity)
{
if (auto err = gnutls_x509_crl_set_version(crl, 2))
throw CryptoException(std::string("Unable to set CRL version: ") + gnutls_strerror(err));
@@ -1893,28 +1869,19 @@ RevocationList::sign(const PrivateKey& key, const Certificate& ca, duration vali
throw CryptoException(std::string("Unable to set CRL update time: ") + gnutls_strerror(err));
if (auto err = gnutls_x509_crl_set_next_update(crl, std::chrono::system_clock::to_time_t(next_update)))
throw CryptoException(std::string("Unable to set CRL next update time: ") + gnutls_strerror(err));
uint64_t number {crlNumber};
uint64_t number {0};
size_t number_sz {sizeof(number)};
unsigned critical {0};
gnutls_x509_crl_get_number(crl, &number, &number_sz, &critical);
if (number == 0) {
// The CRL Number extension holds a DER integer of variable length, so it must be
// read as a big-endian byte string rather than straight into an uint64_t.
auto current = getNumber();
for (uint8_t byte : current)
number = (number << 8) | byte;
if (number == 0) {
// Initialize to a random number.
std::random_device rdev;
do {
std::generate_n((uint8_t*) &number, sizeof(number), std::bind(rand_byte, std::ref(rdev)));
} while (number == 0);
} else
number++;
}
// Store the number in network byte order.
uint8_t packedNumber[sizeof(number)];
for (size_t i = 0; i < sizeof(packedNumber); i++)
packedNumber[i] = (uint8_t) ((number >> (8 * (sizeof(packedNumber) - 1 - i))) & 0xff);
if (auto err = gnutls_x509_crl_set_number(crl, packedNumber, sizeof(packedNumber)))
throw CryptoException(std::string("Unable to set CRL number: ") + gnutls_strerror(err));
// initialize to a random number
number_sz = sizeof(number);
std::random_device rdev;
std::generate_n((uint8_t*) &number, sizeof(number), std::bind(rand_byte, std::ref(rdev)));
} else
number = endian(endian(number) + 1);
if (auto err = gnutls_x509_crl_set_number(crl, &number, sizeof(number)))
throw CryptoException(std::string("Unable to set CRL update time: ") + gnutls_strerror(err));
if (auto err = gnutls_x509_crl_sign2(crl, ca.cert, key.x509_key, GNUTLS_DIG_SHA512, 0))
throw CryptoException(std::string("Unable to sign certificate revocation list: ") + gnutls_strerror(err));
// to be able to actually use the CRL we need to serialize/deserialize it
@@ -2078,7 +2045,7 @@ hkdf_extract(const Blob& salt, const Blob& ikm)
hmac_sha256_set_key(&ctx, salt.size(), salt.data());
hmac_sha256_update(&ctx, ikm.size(), ikm.data());
Blob prk(SHA256_DIGEST_SIZE);
hmacSha256Digest(&ctx, prk.data());
hmac_sha256_digest(&ctx, SHA256_DIGEST_SIZE, prk.data());
return prk;
}
@@ -2096,7 +2063,7 @@ hkdf_expand(const Blob& prk, const uint8_t* info, size_t info_len, size_t len)
hmac_sha256_update(&ctx, info_len, info);
hmac_sha256_update(&ctx, 1, &i);
T.resize(SHA256_DIGEST_SIZE);
hmacSha256Digest(&ctx, T.data());
hmac_sha256_digest(&ctx, SHA256_DIGEST_SIZE, T.data());
okm.insert(okm.end(), T.begin(), T.end());
i++;
}
@@ -2256,7 +2223,7 @@ webPushEncrypt(const Blob& p256dh,
Blob ciphertext(plaintext.size() + GCM_DIGEST_SIZE);
gcm_aes128_encrypt(&aes, plaintext.size(), ciphertext.data(), plaintext.data());
gcmAes128Digest(&aes, ciphertext.data() + plaintext.size());
gcm_aes128_digest(&aes, GCM_DIGEST_SIZE, ciphertext.data() + plaintext.size());
// 6. Construct Output
Blob output;
+9 -40
View File
@@ -661,9 +661,8 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
/* on new values */
if (auto sr = ws.lock()) {
scheduler.edit(sr->nextSearchStep, scheduler.time());
sr->insertNode(node, scheduler.time(), answer.ntoken);
if (auto sn = sr->getNode(node)) {
if (not answer.ntoken.empty())
sn->refreshToken(answer.ntoken);
sn->onValues(query, std::move(answer), types, scheduler);
}
}
@@ -1432,8 +1431,6 @@ Dht::storageStore(const InfoHash& id,
created = std::min(created, now);
if (expiration == time_point::min())
expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
else if (not permanent)
expiration = std::min(expiration, created + getType(value->type).expiration);
if (expiration < now)
return false;
@@ -2270,16 +2267,8 @@ Dht::onDisconnected()
void
Dht::bootstrap()
{
if (dht4.status != NodeStatus::Disconnected || dht6.status != NodeStatus::Disconnected) {
// The job that led here has already run, and the scheduler does not
// clear the handle to a job it has executed. onDisconnected() reads a
// set handle as "a bootstrap is already scheduled" and does nothing,
// so leaving it set here means the node never tries to rejoin the
// network once it has lost it. Clearing it is what says that no
// bootstrap is pending any more.
bootstrapJob.reset();
if (dht4.status != NodeStatus::Disconnected || dht6.status != NodeStatus::Disconnected)
return;
}
if (logger_)
logger_->debug("[{}] Bootstraping", myid.to_view());
bootstrap_pending = false;
@@ -2363,9 +2352,6 @@ Dht::confirmNodes()
std::vector<ValuesExport>
Dht::exportValues() const
{
const auto& now = scheduler.time();
const auto system_now = system_clock::now();
std::vector<ValuesExport> e {};
e.reserve(store.size());
for (const auto& h : store) {
@@ -2378,7 +2364,7 @@ Dht::exportValues() const
pk.pack_array(vals.size());
for (const auto& v : vals) {
pk.pack_array(4);
pk.pack(to_system_time(v.created, now, system_now));
pk.pack(v.created.time_since_epoch().count());
v.data->msgpack_pack(pk);
if (const auto& store_addr = v.store_bucket ? v.store_bucket->getAddr() : SockAddr {}) {
pk.pack_bin(store_addr.getLength());
@@ -2386,7 +2372,7 @@ Dht::exportValues() const
} else {
pk.pack_bin(0);
}
pk.pack(to_system_time(v.expiration, now, system_now));
pk.pack(v.expiration.time_since_epoch().count());
}
ve.second = {buffer.data(), buffer.data() + buffer.size()};
e.push_back(std::move(ve));
@@ -2398,7 +2384,6 @@ void
Dht::importValues(const std::vector<ValuesExport>& import)
{
const auto& now = scheduler.time();
const auto system_now = system_clock::now();
unsigned imported = 0, ignored = 0;
size_t imported_size = 0;
@@ -2423,15 +2408,8 @@ Dht::importValues(const std::vector<ValuesExport>& import)
Value tmp_val;
SockAddr store_addr;
try {
const bool uses_system_time = valel.via.array.ptr[0].type == msgpack::type::EXT;
if (uses_system_time) {
val_time = from_system_time(valel.via.array.ptr[0].as<system_clock::time_point>(),
now,
system_now);
} else {
val_time = time_point {
time_point::duration {valel.via.array.ptr[0].as<time_point::duration::rep>()}};
}
val_time = time_point {
time_point::duration {valel.via.array.ptr[0].as<time_point::duration::rep>()}};
tmp_val.msgpack_unpack(valel.via.array.ptr[1]);
if (valel.via.array.size >= 3) {
auto addr_blob = valel.via.array.ptr[2].as<Blob>();
@@ -2441,14 +2419,8 @@ Dht::importValues(const std::vector<ValuesExport>& import)
}
}
if (valel.via.array.size >= 4) {
if (uses_system_time) {
expiration = from_system_time(valel.via.array.ptr[3].as<system_clock::time_point>(),
now,
system_now);
} else {
expiration = time_point {
time_point::duration {valel.via.array.ptr[3].as<time_point::duration::rep>()}};
}
expiration = time_point {
time_point::duration {valel.via.array.ptr[3].as<time_point::duration::rep>()}};
}
} catch (const std::exception&) {
if (logger_)
@@ -2567,7 +2539,6 @@ Dht::onError(Sp<net::Request> req, net::DhtProtocolException e)
if (listen.second.req and listen.second.req->pending())
n->node->cancelRequest(listen.second.req);
listen.second.req = {};
n->node->closeSocket(listen.second.socketId);
if (listen.second.refresh) {
listen.second.refresh->cancel();
listen.second.refresh = {};
@@ -2744,9 +2715,7 @@ Dht::onListenDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search>&
if (not sr->done) {
const auto& now = scheduler.time();
if (not answer.ntoken.empty())
if (auto sn = sr->getNode(node))
sn->refreshToken(answer.ntoken);
sr->insertNode(node, now, answer.ntoken);
searchSendGetValues(sr);
scheduler.edit(sr->nextSearchStep, now);
}
+45 -45
View File
@@ -195,7 +195,7 @@ DhtProxyClient::stop()
if (not isDestroying_.exchange(true)) {
std::shared_ptr<http::Resolver> resolver;
{
std::lock_guard l(resolverLock_);
std::lock_guard<std::mutex> l(resolverLock_);
resolver = std::move(resolver_);
}
if (resolver)
@@ -204,7 +204,7 @@ DhtProxyClient::stop()
if (infoState_)
infoState_->cancel = true;
{
std::lock_guard lock(requestLock_);
std::lock_guard<std::mutex> lock(requestLock_);
for (auto& request : requests_)
request.second->cancel();
}
@@ -219,7 +219,7 @@ DhtProxyClient::stop()
std::vector<Sp<Value>>
DhtProxyClient::getLocal(const InfoHash& k, const Value::Filter& filter) const
{
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(k);
if (s == searches_.end())
return {};
@@ -229,7 +229,7 @@ DhtProxyClient::getLocal(const InfoHash& k, const Value::Filter& filter) const
Sp<Value>
DhtProxyClient::getLocalById(const InfoHash& k, Value::Id id) const
{
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(k);
if (s == searches_.end())
return {};
@@ -239,7 +239,7 @@ DhtProxyClient::getLocalById(const InfoHash& k, Value::Id id) const
void
DhtProxyClient::cancelAllListeners()
{
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
if (logger_)
logger_->debug("[proxy:client] [listeners] [{} searches] cancel all", searches_.size());
for (auto& s : searches_) {
@@ -266,7 +266,7 @@ DhtProxyClient::shutdown(ShutdownCallback cb, bool)
NodeStatus
DhtProxyClient::getStatus(sa_family_t af) const
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
switch (af) {
case AF_INET:
return statusIpv4_;
@@ -280,7 +280,7 @@ DhtProxyClient::getStatus(sa_family_t af) const
bool
DhtProxyClient::isRunning(sa_family_t af) const
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
switch (af) {
case AF_INET:
return statusIpv4_ != NodeStatus::Disconnected;
@@ -297,7 +297,7 @@ DhtProxyClient::periodic(const uint8_t*, size_t, SockAddr, const time_point& /*n
// Exec all currently stored callbacks
decltype(callbacks_) callbacks;
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks = std::move(callbacks_);
}
for (auto& callback : callbacks)
@@ -357,7 +357,7 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
}
if (not values.empty() and cb) {
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([opstate, cb, values = std::move(values)]() {
if (not opstate->stop.load() and not cb(values)) {
opstate->stop.store(true);
@@ -382,7 +382,7 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
}
if (donecb) {
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([donecb, opstate]() {
donecb(opstate->ok, {});
opstate->stop.store(true);
@@ -391,12 +391,12 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
loopSignal_();
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -419,7 +419,7 @@ DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_po
std::shared_ptr<std::atomic_bool> ok;
if (permanent) {
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
ok = std::make_shared<std::atomic_bool>(true);
auto& search = searches_[key];
if (val->id) {
@@ -443,7 +443,7 @@ DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_po
if (ok)
*ok = result;
if (cb) {
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([cb, result]() { cb(result, {}); });
}
loopSignal_();
@@ -464,7 +464,7 @@ DhtProxyClient::handleRefreshPut(const asio::error_code& ec, InfoHash key, Value
}
if (logger_)
logger_->debug("[proxy:client] [put] [refresh {}]", key.to_view());
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto search = searches_.find(key);
if (search != searches_.end()) {
auto p = search->second.puts.find(id);
@@ -480,7 +480,7 @@ DhtProxyClient::handleRefreshPut(const asio::error_code& ec, InfoHash key, Value
std::shared_ptr<http::Request>
DhtProxyClient::buildRequest(const std::string& target)
{
std::unique_lock l(resolverLock_);
std::unique_lock<std::mutex> l(resolverLock_);
auto resolver = resolver_;
l.unlock();
if (not resolver)
@@ -535,7 +535,7 @@ DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallbackSimple cb,
auto id = dht::Value(parsedValue).id;
val->id = id;
if (permanent) {
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto& search = searches_[key];
auto it = search.pendingPuts.find(val);
if (it != search.pendingPuts.end()) {
@@ -565,12 +565,12 @@ DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallbackSimple cb,
if (cb)
cb(ok);
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -640,7 +640,7 @@ DhtProxyClient::getProxyInfos()
logger_->debug("[proxy:client] [info] requesting proxy server node information");
auto infoState = std::make_shared<InfoState>();
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
if (infoState_)
infoState_->cancel = true;
infoState_ = infoState;
@@ -655,7 +655,7 @@ DhtProxyClient::getProxyInfos()
auto resolver = std::make_shared<http::Resolver>(httpContext_, proxyUrl_, logger_);
queryProxyInfo(infoState, resolver, AF_INET);
queryProxyInfo(infoState, resolver, AF_INET6);
std::lock_guard l(resolverLock_);
std::lock_guard<std::mutex> l(resolverLock_);
resolver_ = resolver;
}
@@ -701,7 +701,7 @@ DhtProxyClient::queryProxyInfo(const Sp<InfoState>& infoState, const Sp<http::Re
}
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
@@ -709,7 +709,7 @@ DhtProxyClient::queryProxyInfo(const Sp<InfoState>& infoState, const Sp<http::Re
if (infoState->cancel.load())
return;
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -724,7 +724,7 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, const sa_family_t fa
{
if (isDestroying_)
return;
std::unique_lock l(lockCurrentProxyInfos_);
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
auto oldStatus = std::max(statusIpv4_, statusIpv6_);
auto& status = family == AF_INET ? statusIpv4_ : statusIpv6_;
auto& pubAddress = family == AF_INET ? publicAddressV4_ : publicAddressV6_;
@@ -761,7 +761,7 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, const sa_family_t fa
addresses.emplace_back(publicAddressV4_);
if (publicAddressV6_)
addresses.emplace_back(publicAddressV6_);
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back(
[cb = publicAddressChangedCb_, addresses = std::move(addresses)]() { cb(std::move(addresses)); });
}
@@ -785,7 +785,7 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, const sa_family_t fa
listenerRestartTimer_->expires_at(std::chrono::steady_clock::now());
listenerRestartTimer_->async_wait(std::bind(&DhtProxyClient::restartListeners, this, std::placeholders::_1));
if (not onConnectCallbacks_.empty()) {
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([cbs = std::move(onConnectCallbacks_)]() mutable {
while (not cbs.empty()) {
cbs.front()();
@@ -820,7 +820,7 @@ DhtProxyClient::parsePublicAddress(const Json::Value& val)
std::vector<SockAddr>
DhtProxyClient::getPublicAddress(sa_family_t family)
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
std::vector<SockAddr> result;
if (publicAddressV6_ && family != AF_INET)
result.emplace_back(publicAddressV6_);
@@ -837,7 +837,7 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
if (isDestroying_)
return 0;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto& search = searches_[key];
auto query = std::make_shared<Query>(Select {}, std::move(where));
return search.ops
@@ -874,7 +874,7 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
system_clock::time_point t) {
if (opstate->stop)
return false;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(key);
if (s != searches_.end()) {
auto l = s->second.listeners.find(token);
@@ -929,7 +929,7 @@ DhtProxyClient::handleResubscribe(const asio::error_code& ec,
}
if (opstate->stop)
return;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(key);
if (s != searches_.end()) {
auto l = s->second.listeners.find(token);
@@ -948,7 +948,7 @@ DhtProxyClient::cancelListen(const InfoHash& key, size_t gtoken)
if (logger_)
logger_->debug("[proxy:client] [search {}] cancel listen {}", key.to_view(), gtoken);
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
// find the listener in cache
auto it = searches_.find(key);
if (it == searches_.end())
@@ -979,7 +979,7 @@ DhtProxyClient::handleExpireListener(const asio::error_code& ec, const InfoHash&
if (logger_)
logger_->debug("[proxy:client] [listen {}] expire listener", key.to_view());
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto search = searches_.find(key);
if (search == searches_.end())
return;
@@ -1015,12 +1015,12 @@ DhtProxyClient::handleExpireListener(const asio::error_code& ec, const InfoHash&
opFailed();
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -1097,7 +1097,7 @@ DhtProxyClient::sendListen(const restinio::http_request_header_t& header,
if (cb) {
auto expired = json.get("expired", Json::Value(false)).asBool();
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([cb, value, opstate, expired]() {
if (not opstate->stop.load()
and not cb({value}, expired, system_clock::time_point::min()))
@@ -1124,12 +1124,12 @@ DhtProxyClient::sendListen(const restinio::http_request_header_t& header,
opFailed();
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->add_on_status_callback([request, seconds = this->listenKeepIdle()](unsigned status_code) {
@@ -1153,7 +1153,7 @@ DhtProxyClient::opFailed()
if (logger_)
logger_->error("[proxy:client] proxy request failed");
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
statusIpv4_ = NodeStatus::Disconnected;
statusIpv6_ = NodeStatus::Disconnected;
}
@@ -1186,7 +1186,7 @@ DhtProxyClient::restartListeners(const asio::error_code& ec)
if (logger_)
logger_->d("[proxy:client] [listeners] refresh permanent puts");
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
for (auto& search : searches_) {
const auto& key = search.first;
for (auto& put : search.second.puts) {
@@ -1249,7 +1249,7 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
#ifdef OPENDHT_PUSH_NOTIFICATIONS
{
// If a push notification is received, the proxy is up and running
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
auto oldStatus = std::max(statusIpv4_, statusIpv6_);
if (oldStatus != NodeStatus::Connected)
launchConnectedCbs_ = true;
@@ -1264,7 +1264,7 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
logger_->debug("[proxy:client] [push] ignoring push for other session");
return PushNotificationResult::IgnoredWrongSession;
}
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto timeout = notification.find("timeout");
if (timeout != notification.cend()) {
InfoHash key(timeout->second);
@@ -1326,11 +1326,11 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
ids.emplace_back(std::stoull(substr));
}
{
std::lock_guard lockCb(lockCallbacks_);
std::lock_guard<std::mutex> lockCb(lockCallbacks_);
callbacks_.emplace_back([this, key, token, opstate, ids, sendTime]() {
if (opstate->stop)
return;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(key);
if (s == searches_.end())
return;
@@ -1423,7 +1423,7 @@ DhtProxyClient::setPushNotificationToken([[maybe_unused]] const std::string& tok
{
#ifdef OPENDHT_PUSH_NOTIFICATIONS
{
std::unique_lock l(lockCurrentProxyInfos_);
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
if (deviceKey_ == token)
return;
deviceKey_ = token;
@@ -1432,7 +1432,7 @@ DhtProxyClient::setPushNotificationToken([[maybe_unused]] const std::string& tok
}
if (logger_)
logger_->debug("[proxy:client] [push] token changed, resubscribing");
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
for (auto& search : searches_) {
for (auto& listener : search.second.listeners) {
resubscribe(search.first, listener.first, listener.second);
+33 -31
View File
@@ -176,7 +176,7 @@ DhtProxyServer::ConnectionListener::state_changed(const restinio::connection_sta
void
DhtProxyServer::onConnectionClosed(restinio::connection_id_t id)
{
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
auto it = listeners_.find(id);
if (it != listeners_.end()) {
dht_->cancelListen(it->second.hash, std::move(it->second.token));
@@ -381,13 +381,13 @@ DhtProxyServer::saveState(Os& stream)
msgpack::packer<Os> pk(&stream);
pk.pack_map(2);
{
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
pk.pack("puts");
pk.pack(puts_);
}
#ifdef OPENDHT_PUSH_NOTIFICATIONS
{
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
pk.pack("pushListeners");
pk.pack(pushListeners_);
}
@@ -408,7 +408,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
if (oh.get().type != msgpack::type::MAP)
continue;
if (auto puts = findMapValue(oh.get(), "puts"sv)) {
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
puts_ = puts->as<decltype(puts_)>();
if (logger_)
logger_->debug("[proxy:server] Loading {} persistent puts", puts_.size());
@@ -432,7 +432,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
json["to"] = clientId;
json["vid"] = std::to_string(vid);
if (sessionCtx) {
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
}
return json;
@@ -459,7 +459,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
}
#ifdef OPENDHT_PUSH_NOTIFICATIONS
if (auto pushListeners = findMapValue(oh.get(), "pushListeners"sv)) {
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
pushListeners_ = pushListeners->as<decltype(pushListeners_)>();
if (logger_)
logger_->debug("[proxy:server] Loading {} push listeners", pushListeners_.size());
@@ -496,7 +496,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
Json::Value json;
json["timeout"] = infoHash;
json["to"] = clientId;
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
return json;
};
@@ -546,7 +546,7 @@ DhtProxyServer::~DhtProxyServer()
saveState(stateFile);
}
if (dht_) {
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
for (auto& l : listeners_) {
dht_->cancelListen(l.second.hash, std::move(l.second.token));
if (l.second.response)
@@ -817,7 +817,7 @@ DhtProxyServer::listen(restinio::request_handle_t request, restinio::router::rou
auto response = std::make_shared<ResponseByPartsBuilder>(
initHttpResponse(request->create_response<ResponseByParts>()));
response->flush();
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
// save the listener to handle a disconnect
auto& session = listeners_[request->connection_id()];
session.hash = infoHash;
@@ -931,7 +931,7 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
logger_->debug("[proxy:server] [subscribe {}] [client {}] [session {}]", infoHash, clientId, sessionId);
// Insert new or return existing push listeners of a token
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
auto& pushListener = pushListeners_[pushToken];
auto& pushListeners = pushListener.listeners[infoHash];
@@ -945,7 +945,7 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
listIt->clientId = clientId;
listIt->sessionCtx = std::make_shared<PushSessionContext>(sessionId);
} else {
std::lock_guard l(listIt->sessionCtx->lock);
std::lock_guard<std::mutex> l(listIt->sessionCtx->lock);
listIt->sessionCtx->sessionId = sessionId;
}
auto& listener = *listIt;
@@ -963,7 +963,7 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
Json::Value json;
json["timeout"] = h;
json["to"] = clientId;
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
return json;
};
@@ -1110,7 +1110,7 @@ DhtProxyServer::handleCancelPushListen(const asio::error_code& ec,
}
if (logger_)
logger_->debug("[proxy:server] [listen:push {}] cancelled for {}", key, clientId);
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
auto pushListener = pushListeners_.find(pushToken);
if (pushListener == pushListeners_.end())
@@ -1151,24 +1151,19 @@ DhtProxyServer::handlePushListen(const InfoHash& infoHash,
json["t"] = Json::Value::Int64(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
{
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
}
// Build a comma-separated list of ids from the values. This is intended to be used when
// streaming from the iOS notification extension in order to filter out unwanted values.
std::string ids;
std::string pushTypes;
for (const auto& value : values) {
if (!ids.empty()) {
if (!ids.empty())
ids += ",";
pushTypes += ",";
}
pushTypes += value->pushType;
ids += std::to_string(value->id);
}
json["ids"] = ids;
json["pt"] = pushTypes;
// If message is expired copy the value ID to the `exp` field.
// This is presumably used by the Android notification system.
@@ -1205,7 +1200,14 @@ DhtProxyServer::sendPushNotification(
try {
std::shared_ptr<http::Request> request;
if (type == PushType::UnifiedPush) {
request = std::make_shared<http::Request>(io_context(), token, logger_);
http::Url tokenUrl(token);
request = std::make_shared<http::Request>(io_context(),
concat(tokenUrl.protocol, "://"sv, tokenUrl.host),
tokenUrl.service,
tokenUrl.protocol.find("https") == 0,
logger_);
request->set_target(tokenUrl.target);
request->set_header_field(restinio::http_field_t::host, std::string(tokenUrl.host));
} else {
request = std::make_shared<http::Request>(io_context(),
pushHostPort_.first,
@@ -1224,8 +1226,8 @@ DhtProxyServer::sendPushNotification(
Blob auth; ///< Auth secret
auto delim = topicView.find('|');
if (delim != std::string_view::npos) {
pubKey = base64_default_or_url_decode(topicView.substr(0, delim));
auth = base64_default_or_url_decode(topicView.substr(delim + 1));
pubKey = base64_decode(topicView.substr(0, delim));
auth = base64_decode(topicView.substr(delim + 1));
}
request->set_header_field(restinio::http_field_t::ttl, "86400");
@@ -1300,12 +1302,12 @@ DhtProxyServer::sendPushNotification(
if (state == http::Request::State::DONE) {
if (logger_ and response.status_code != 200)
logger_->error("[proxy:server] [notification] push failed: {}", response.status_code);
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -1328,7 +1330,7 @@ DhtProxyServer::sendPushNotification(
if (logger_)
logger_->error("[proxy:server] [notification] error send push: {}", e.what());
if (reqid) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
}
@@ -1347,7 +1349,7 @@ DhtProxyServer::handleCancelPermamentPut(const asio::error_code& ec, const InfoH
}
if (logger_)
logger_->debug("[proxy:server] [put {}] cancel permament put {}", key, vid);
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
auto sPuts = puts_.find(key);
if (sPuts == puts_.end())
return;
@@ -1404,7 +1406,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
sessionId = pVal["session_id"].asString();
topic = pVal["topic"].asString();
}
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
auto timeout = std::chrono::steady_clock::now() + proxy::OP_TIMEOUT;
auto& sPuts = puts_[infoHash];
if (value->id == Value::INVALID_ID) {
@@ -1421,7 +1423,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
if (not pp.second.sessionCtx)
pp.second.sessionCtx = std::make_shared<PushSessionContext>(sessionId);
else {
std::lock_guard l(pp.second.sessionCtx->lock);
std::lock_guard<std::mutex> l(pp.second.sessionCtx->lock);
pp.second.sessionCtx->sessionId = sessionId;
}
}
@@ -1456,7 +1458,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
if (not pput.sessionCtx)
pput.sessionCtx = std::make_shared<PushSessionContext>(sessionId);
else {
std::lock_guard l(pput.sessionCtx->lock);
std::lock_guard<std::mutex> l(pput.sessionCtx->lock);
pput.sessionCtx->sessionId = sessionId;
}
}
@@ -1473,7 +1475,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
json["timeout"] = infoHash.toString();
json["to"] = clientId;
json["vid"] = std::to_string(vid);
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
return json;
};
+170 -114
View File
@@ -21,6 +21,16 @@ namespace dht {
using namespace std::literals;
constexpr auto PEER_DISCOVERY_DHT_SERVICE = "dht"sv;
struct NodeInsertionPack
{
dht::InfoHash nodeId;
in_port_t port;
dht::NetId net;
MSGPACK_DEFINE(nodeId, port, net)
};
DhtRunner::DhtRunner()
: dht_()
{
@@ -66,7 +76,7 @@ DhtRunner::run(const char* ip4, const char* ip6, const char* service, Config& co
void
DhtRunner::run(const Config& config, Context&& context)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
auto expected = State::Idle;
if (not running.compare_exchange_strong(expected, State::Running)) {
if (context.logger)
@@ -89,11 +99,6 @@ DhtRunner::run(const Config& config, Context&& context)
auto state_path = config.dht_config.node_config.persist_path;
if (not state_path.empty())
state_path += "_port.txt";
// Ports loaded from the persisted state are a best-effort hint to keep a
// stable identity across restarts; remember which ones we set so that we
// can fall back to random ports if they are already in use (e.g. a fast
// restart while the previous instance still holds them).
bool triedSavedPort = false;
if (not state_path.empty() && (local4.getPort() == 0 || local6.getPort() == 0)) {
std::ifstream inConfig(state_path);
if (inConfig.is_open()) {
@@ -105,7 +110,6 @@ DhtRunner::run(const Config& config, Context&& context)
fmt::ptr(this),
port);
local4.setPort(port);
triedSavedPort = true;
}
}
if (inConfig >> port) {
@@ -115,7 +119,6 @@ DhtRunner::run(const Config& config, Context&& context)
fmt::ptr(this),
port);
local6.setPort(port);
triedSavedPort = true;
}
}
}
@@ -133,25 +136,12 @@ DhtRunner::run(const Config& config, Context&& context)
if (config.proxy_server.empty()) {
if (not context.sock) {
try {
context.sock.reset(new net::UdpSocket(local4, local6, context.logger));
} catch (const DhtException&) {
if (not triedSavedPort)
throw;
// The saved ports are unavailable (typically still held by a
// previous instance): fall back to OS-assigned ports.
if (context.logger)
context.logger->w("[runner %p] Saved ports unavailable, falling back to random ports",
fmt::ptr(this));
local4.setPort(0);
local6.setPort(0);
context.sock.reset(new net::UdpSocket(local4, local6, context.logger));
}
context.sock.reset(new net::UdpSocket(local4, local6, context.logger));
}
context.sock->setOnReceive([&](net::PacketList&& pkts) {
net::PacketList ret;
{
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
rcv.splice(rcv.end(), std::move(pkts));
size_t dropped = 0;
while (rcv.size() > net::RX_QUEUE_MAX_SIZE) {
@@ -196,6 +186,9 @@ DhtRunner::run(const Config& config, Context&& context)
if (context.certificateStore) {
dht_->setLocalCertificateStore(std::move(context.certificateStore));
}
if (context.certificateStorePkId) {
dht_->setLocalCertificateStore(std::move(context.certificateStorePkId));
}
if (context.publicAddressChangedCb) {
dht_->setOnPublicAddressChanged(std::move(context.publicAddressChangedCb));
}
@@ -204,19 +197,19 @@ DhtRunner::run(const Config& config, Context&& context)
return;
dht_thread = std::thread([this]() {
while (running != State::Idle) {
std::unique_lock lk(dht_mtx);
std::unique_lock<std::mutex> lk(dht_mtx);
time_point wakeup = loop_();
auto hasJobToDo = [this]() {
if (running == State::Idle)
return true;
{
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
if (not rcv.empty())
return true;
}
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (not pending_ops_prio.empty())
return true;
auto s = getStatus();
@@ -237,7 +230,7 @@ DhtRunner::run(const Config& config, Context&& context)
if (config.peer_discovery or config.peer_publish) {
#ifdef OPENDHT_PEER_DISCOVERY
peerDiscovery_ = context.peerDiscovery ? std::move(context.peerDiscovery)
: std::make_shared<PeerDiscovery>(nullptr, context.logger);
: std::make_shared<PeerDiscovery>();
#else
std::cerr << "Peer discovery requested but OpenDHT built without peer discovery support." << std::endl;
#endif
@@ -246,22 +239,32 @@ DhtRunner::run(const Config& config, Context&& context)
#ifdef OPENDHT_PEER_DISCOVERY
auto netId = config.dht_config.node_config.network;
if (config.peer_discovery) {
peerDiscovery_->startDiscovery(netId, [this](const InfoHash& nodeId, SockAddr&& address) {
if (nodeId != dht_->getNodeId())
bootstrap(nodeId, address);
});
peerDiscovery_->startDiscovery<NodeInsertionPack>(PEER_DISCOVERY_DHT_SERVICE,
[this, netId](NodeInsertionPack&& v, SockAddr&& addr) {
addr.setPort(v.port);
if (v.nodeId != dht_->getNodeId() && netId == v.net) {
bootstrap(v.nodeId, addr);
}
});
}
if (config.peer_publish) {
msgpack::sbuffer sbuf_node;
NodeInsertionPack adc;
adc.net = netId;
adc.nodeId = dht_->getNodeId();
if (auto socket = dht_->getSocket()) {
const auto& bound4 = socket->getBoundRef(AF_INET);
const auto& bound6 = socket->getBoundRef(AF_INET6);
const auto port4 = bound4 ? bound4.getPort() : 0;
const auto port6 = bound6 ? bound6.getPort() : 0;
if (port4 and port6 and port4 != port6) {
if (logger_)
logger_->error("Unable to publish peer discovery with different IPv4 and IPv6 ports");
} else if (const auto port = port4 ? port4 : port6) {
peerDiscovery_->startPublish(dht_->getNodeId(), netId, port);
// IPv4
if (const auto& bound4 = socket->getBoundRef(AF_INET)) {
adc.port = bound4.getPort();
msgpack::pack(sbuf_node, adc);
peerDiscovery_->startPublish(AF_INET, PEER_DISCOVERY_DHT_SERVICE, sbuf_node);
}
// IPv6
if (const auto& bound6 = socket->getBoundRef(AF_INET6)) {
adc.port = bound6.getPort();
sbuf_node.clear();
msgpack::pack(sbuf_node, adc);
peerDiscovery_->startPublish(AF_INET6, PEER_DISCOVERY_DHT_SERVICE, sbuf_node);
}
}
}
@@ -281,7 +284,7 @@ DhtRunner::run(const Config& config, Context&& context)
void
DhtRunner::shutdown(ShutdownCallback cb, bool stop)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
auto expected = State::Running;
if (not running.compare_exchange_strong(expected, State::Stopping)) {
if (expected == State::Stopping and ongoing_ops) {
@@ -341,7 +344,7 @@ DhtRunner::checkShutdown()
{
decltype(shutdownCallbacks_) cbs;
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Stopping or ongoing_ops)
return false;
cbs = std::move(shutdownCallbacks_);
@@ -356,7 +359,7 @@ void
DhtRunner::join()
{
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (running.exchange(State::Idle) == State::Idle)
return;
cv.notify_all();
@@ -375,7 +378,7 @@ DhtRunner::join()
dht_thread.join();
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (ongoing_ops and logger_) {
logger_->warn("[runner {:p}] stopping with {:d} remaining ops", fmt::ptr(this), ongoing_ops.load());
}
@@ -385,7 +388,7 @@ DhtRunner::join()
shutdownCallbacks_.clear();
}
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
resetDht();
status4 = NodeStatus::Disconnected;
status6 = NodeStatus::Disconnected;
@@ -395,7 +398,7 @@ DhtRunner::join()
SockAddr
DhtRunner::getBound(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
if (auto sock = dht_->getSocket())
return sock->getBound(af);
@@ -405,7 +408,7 @@ DhtRunner::getBound(sa_family_t af) const
in_port_t
DhtRunner::getBoundPort(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
if (auto sock = dht_->getSocket())
return sock->getPort(af);
@@ -415,7 +418,7 @@ DhtRunner::getBoundPort(sa_family_t af) const
void
DhtRunner::dumpTables() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->dumpTables();
}
@@ -440,7 +443,7 @@ DhtRunner::getNodeId() const
std::pair<size_t, size_t>
DhtRunner::getStoreSize() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
return {};
return dht_->getStoreSize();
@@ -449,7 +452,7 @@ DhtRunner::getStoreSize() const
void
DhtRunner::setStorageLimit(size_t limit)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->setStorageLimit(limit);
@@ -458,7 +461,7 @@ DhtRunner::setStorageLimit(size_t limit)
size_t
DhtRunner::getStorageLimit() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->getStorageLimit();
@@ -467,7 +470,7 @@ DhtRunner::getStorageLimit() const
void
DhtRunner::setLocalStorageLimit(size_t limit)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->setLocalStorageLimit(limit);
@@ -476,7 +479,7 @@ DhtRunner::setLocalStorageLimit(size_t limit)
size_t
DhtRunner::getLocalStorageLimit() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->getLocalStorageLimit();
@@ -485,7 +488,7 @@ DhtRunner::getLocalStorageLimit() const
std::vector<NodeExport>
DhtRunner::exportNodes() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
return {};
return dht_->exportNodes();
@@ -494,7 +497,7 @@ DhtRunner::exportNodes() const
std::vector<ValuesExport>
DhtRunner::exportValues() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
return {};
return dht_->exportValues();
@@ -503,7 +506,7 @@ DhtRunner::exportValues() const
void
DhtRunner::setLogger(const Sp<Logger>& logger)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
logger_ = logger;
if (dht_)
dht_->setLogger(logger);
@@ -512,7 +515,7 @@ DhtRunner::setLogger(const Sp<Logger>& logger)
void
DhtRunner::setLogFilter(const InfoHash& f)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->setLogFilter(f);
}
@@ -520,28 +523,22 @@ DhtRunner::setLogFilter(const InfoHash& f)
void
DhtRunner::registerType(const ValueType& type)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->registerType(type);
}
void
DhtRunner::registerInsecureType(const ValueType& type)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->registerInsecureType(type);
}
void
DhtRunner::importValues(const std::vector<ValuesExport>& values)
{
{
std::lock_guard lck(dht_mtx);
dht_->importValues(values);
}
// Recompute the runner wakeup after import schedules value expirations.
std::lock_guard lck(storage_mtx);
pending_ops_prio.emplace([](SecureDht&) {});
cv.notify_all();
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->importValues(values);
}
unsigned
@@ -551,7 +548,7 @@ DhtRunner::getNodesStats(sa_family_t af,
unsigned* cached_return,
unsigned* incoming_return) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
const auto stats = dht_->getNodesStats(af);
if (good_return)
*good_return = stats.good_nodes;
@@ -567,7 +564,7 @@ DhtRunner::getNodesStats(sa_family_t af,
NodeStats
DhtRunner::getNodesStats(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getNodesStats(af);
}
@@ -575,7 +572,7 @@ NodeInfo
DhtRunner::getNodeInfo() const
{
NodeInfo info {};
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
info = dht_->getNodeInfo();
info.ongoing_ops = ongoing_ops;
@@ -585,7 +582,7 @@ DhtRunner::getNodeInfo() const
void
DhtRunner::getNodeInfo(std::function<void(std::shared_ptr<NodeInfo>)> cb)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
ongoing_ops++;
pending_ops_prio.emplace([cb = std::move(cb), this](SecureDht& dht) {
auto sinfo = std::make_shared<NodeInfo>();
@@ -600,44 +597,44 @@ DhtRunner::getNodeInfo(std::function<void(std::shared_ptr<NodeInfo>)> cb)
std::vector<unsigned>
DhtRunner::getNodeMessageStats(bool in) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getNodeMessageStats(in);
}
std::string
DhtRunner::getStorageLog() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getStorageLog();
}
std::string
DhtRunner::getStorageLog(const InfoHash& f) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getStorageLog(f);
}
std::string
DhtRunner::getRoutingTablesLog(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getRoutingTablesLog(af);
}
std::string
DhtRunner::getSearchesLog(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getSearchesLog(af);
}
std::string
DhtRunner::getSearchLog(const InfoHash& f, sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getSearchLog(f, af);
}
std::vector<SockAddr>
DhtRunner::getPublicAddress(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
return dht_->getPublicAddress(af);
return {};
@@ -654,7 +651,7 @@ DhtRunner::getPublicAddressStr(sa_family_t af) const
void
DhtRunner::getPublicAddress(std::function<void(std::vector<SockAddr>&&)> cb, sa_family_t af)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
ongoing_ops++;
pending_ops_prio.emplace([cb = std::move(cb), this, af](SecureDht& dht) {
cb(dht.getPublicAddress(af));
@@ -666,14 +663,22 @@ DhtRunner::getPublicAddress(std::function<void(std::vector<SockAddr>&&)> cb, sa_
void
DhtRunner::registerCertificate(const std::shared_ptr<crypto::Certificate>& cert)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->registerCertificate(cert);
}
void
DhtRunner::setLocalCertificateStore(CertificateStoreQueryLegacy&& query_method)
{
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->setLocalCertificateStore(std::forward<CertificateStoreQueryLegacy>(query_method));
}
void
DhtRunner::setLocalCertificateStore(CertificateStoreQuery&& query_method)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->setLocalCertificateStore(std::forward<CertificateStoreQuery>(query_method));
}
@@ -686,7 +691,7 @@ DhtRunner::loop_()
decltype(pending_ops) ops {};
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
auto s = getStatus();
ops = (pending_ops_prio.empty()
&& (s == NodeStatus::Connected or s == NodeStatus::Disconnected or running == State::Stopping))
@@ -702,7 +707,7 @@ DhtRunner::loop_()
decltype(rcv) received {};
decltype(rcv) received_treated {};
{
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
// move to stack
received = std::move(rcv);
}
@@ -737,7 +742,7 @@ DhtRunner::loop_()
}
if (not received_treated.empty()) {
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
if (rcv_free.size() < net::RX_QUEUE_MAX_SIZE)
rcv_free.splice(rcv_free.end(), std::move(received_treated));
}
@@ -761,7 +766,7 @@ DhtRunner::loop_()
void
DhtRunner::get(InfoHash hash, GetCallback vcb, DoneCallback dcb, Value::Filter f, Where w)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (dcb)
@@ -783,7 +788,7 @@ DhtRunner::get(const std::string& key, GetCallback vcb, DoneCallbackSimple dcb,
void
DhtRunner::query(const InfoHash& hash, QueryCallback cb, DoneCallback done_cb, Query q)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (done_cb)
@@ -801,7 +806,7 @@ std::future<size_t>
DhtRunner::listen(InfoHash hash, ValueCallback vcb, Value::Filter f, Where w)
{
auto ret_token = std::make_shared<std::promise<size_t>>();
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
ret_token->set_value(0);
@@ -823,7 +828,7 @@ DhtRunner::listen(const std::string& key, GetCallback vcb, Value::Filter f, Wher
void
DhtRunner::cancelListen(InfoHash h, size_t token)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
ongoing_ops++;
@@ -837,7 +842,7 @@ DhtRunner::cancelListen(InfoHash h, size_t token)
void
DhtRunner::cancelListen(InfoHash h, std::shared_future<size_t> ftoken)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
ongoing_ops++;
@@ -851,7 +856,7 @@ DhtRunner::cancelListen(InfoHash h, std::shared_future<size_t> ftoken)
void
DhtRunner::put(InfoHash hash, Value&& value, DoneCallback cb, time_point created, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -868,7 +873,7 @@ DhtRunner::put(InfoHash hash, Value&& value, DoneCallback cb, time_point created
void
DhtRunner::put(InfoHash hash, std::shared_ptr<Value> value, DoneCallback cb, time_point created, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -891,7 +896,7 @@ DhtRunner::put(const std::string& key, Value&& value, DoneCallbackSimple cb, tim
void
DhtRunner::cancelPut(const InfoHash& h, Value::Id id)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops.emplace([=](SecureDht& dht) { dht.cancelPut(h, id); });
cv.notify_all();
}
@@ -899,7 +904,7 @@ DhtRunner::cancelPut(const InfoHash& h, Value::Id id)
void
DhtRunner::cancelPut(const InfoHash& h, const std::shared_ptr<Value>& value)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops.emplace([=](SecureDht& dht) { dht.cancelPut(h, value->id); });
cv.notify_all();
}
@@ -907,7 +912,7 @@ DhtRunner::cancelPut(const InfoHash& h, const std::shared_ptr<Value>& value)
void
DhtRunner::putSigned(InfoHash hash, std::shared_ptr<Value> value, DoneCallback cb, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -936,7 +941,7 @@ DhtRunner::putSigned(const std::string& key, Value&& value, DoneCallbackSimple c
void
DhtRunner::putEncrypted(InfoHash hash, const PkId& to, std::shared_ptr<Value> value, DoneCallback cb, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -950,6 +955,35 @@ DhtRunner::putEncrypted(InfoHash hash, const PkId& to, std::shared_ptr<Value> va
cv.notify_all();
}
void
DhtRunner::putEncrypted(InfoHash hash, InfoHash to, std::shared_ptr<Value> value, DoneCallback cb, bool permanent)
{
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
cb(false, {});
return;
}
ongoing_ops++;
pending_ops.emplace([=, cb = std::move(cb), value = std::move(value)](SecureDht& dht) mutable {
dht.putEncrypted(hash, to, value, bindOpDoneCallback(std::move(cb)), permanent);
});
cv.notify_all();
}
void
DhtRunner::putEncrypted(InfoHash hash, InfoHash to, Value&& value, DoneCallback cb, bool permanent)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(value)), std::move(cb), permanent);
}
void
DhtRunner::putEncrypted(const std::string& key, InfoHash to, Value&& value, DoneCallback cb, bool permanent)
{
putEncrypted(InfoHash::get(key), to, std::forward<Value>(value), std::move(cb), permanent);
}
void
DhtRunner::putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
@@ -957,7 +991,7 @@ DhtRunner::putEncrypted(InfoHash hash,
DoneCallback cb,
bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -981,7 +1015,7 @@ DhtRunner::putEncrypted(
void
DhtRunner::bootstrap(const std::string& host, const std::string& service)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([host, service](SecureDht& dht) mutable { dht.addBootstrap(host, service); });
cv.notify_all();
}
@@ -989,7 +1023,7 @@ DhtRunner::bootstrap(const std::string& host, const std::string& service)
void
DhtRunner::bootstrap(const std::string& hostService)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
auto [h, s] = splitPort(hostService);
pending_ops_prio.emplace(
[host = std::string(h), service = std::string(s)](SecureDht& dht) { dht.addBootstrap(host, service); });
@@ -999,7 +1033,7 @@ DhtRunner::bootstrap(const std::string& hostService)
void
DhtRunner::clearBootstrap()
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([](SecureDht& dht) mutable { dht.clearBootstrap(); });
cv.notify_all();
}
@@ -1011,7 +1045,7 @@ DhtRunner::bootstrap(std::vector<SockAddr> nodes, DoneCallbackSimple cb)
cb(false);
return;
}
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
ongoing_ops++;
pending_ops_prio.emplace([cb = bindOpDoneCallback(std::move(cb)), nodes = std::move(nodes)](SecureDht& dht) mutable {
auto rem = cb ? std::make_shared<std::pair<size_t, bool>>(nodes.size(), false) : nullptr;
@@ -1034,7 +1068,7 @@ DhtRunner::bootstrap(std::vector<SockAddr> nodes, DoneCallbackSimple cb)
void
DhtRunner::bootstrap(SockAddr addr, DoneCallbackSimple cb)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -1051,7 +1085,7 @@ DhtRunner::bootstrap(SockAddr addr, DoneCallbackSimple cb)
void
DhtRunner::bootstrap(const InfoHash& id, const SockAddr& address)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
pending_ops_prio.emplace([id, address](SecureDht& dht) mutable { dht.insertNode(id, address); });
@@ -1061,7 +1095,7 @@ DhtRunner::bootstrap(const InfoHash& id, const SockAddr& address)
void
DhtRunner::bootstrap(std::vector<NodeExport> nodes)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
pending_ops_prio.emplace([nodes = std::move(nodes)](SecureDht& dht) {
@@ -1074,7 +1108,7 @@ DhtRunner::bootstrap(std::vector<NodeExport> nodes)
void
DhtRunner::connectivityChanged()
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([=](SecureDht& dht) {
dht.connectivityChanged();
#ifdef OPENDHT_PEER_DISCOVERY
@@ -1085,10 +1119,29 @@ DhtRunner::connectivityChanged()
cv.notify_all();
}
void
DhtRunner::findCertificate(InfoHash hash, std::function<void(const Sp<crypto::Certificate>&)> cb)
{
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
cb({});
return;
}
ongoing_ops++;
pending_ops.emplace([this, hash, cb = std::move(cb)](SecureDht& dht) {
dht.findCertificate(hash, [this, cb = std::move(cb)](const Sp<crypto::Certificate>& crt) {
cb(crt);
opEnded();
});
});
cv.notify_all();
}
void
DhtRunner::findCertificate(PkId hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)> cb)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
return;
@@ -1114,7 +1167,7 @@ void
DhtRunner::setProxyServer(const std::string& proxy, const std::string& pushNodeId)
{
#ifdef OPENDHT_PROXY_CLIENT
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (config_.proxy_server == proxy and config_.push_node_id == pushNodeId)
return;
config_.proxy_server = proxy;
@@ -1141,8 +1194,8 @@ DhtRunner::enableProxy(bool proxify)
config_.client_identity,
[this] {
if (config_.threaded) {
std::lock_guard lck(storage_mtx);
pending_ops_prio.emplace([](SecureDht&) {});
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([=](SecureDht&) mutable {});
cv.notify_all();
}
},
@@ -1154,8 +1207,11 @@ DhtRunner::enableProxy(bool proxify)
config_.push_platform,
logger_);
dht_ = std::make_unique<SecureDht>(std::move(dht_via_proxy), config_.dht_config, identityAnnouncedCb_, logger_);
// and use it
use_proxy = proxify;
} else {
use_proxy = proxify;
}
use_proxy = proxify;
#else
if (proxify)
throw std::runtime_error("DHT proxy requested but OpenDHT built without proxy support.");
@@ -1165,7 +1221,7 @@ DhtRunner::enableProxy(bool proxify)
void
DhtRunner::forwardAllMessages(bool forward)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->forwardAllMessages(forward);
}
@@ -1176,7 +1232,7 @@ DhtRunner::forwardAllMessages(bool forward)
void
DhtRunner::setPushNotificationToken(const std::string& token)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
config_.push_token = token;
if (dht_)
@@ -1189,7 +1245,7 @@ DhtRunner::setPushNotificationToken(const std::string& token)
void
DhtRunner::setPushNotificationTopic(const std::string& topic)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
config_.push_topic = topic;
if (dht_)
@@ -1202,7 +1258,7 @@ DhtRunner::setPushNotificationTopic(const std::string& topic)
void
DhtRunner::setPushNotificationPlatform(const std::string& platform)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
config_.push_platform = platform;
if (dht_)
@@ -1218,7 +1274,7 @@ DhtRunner::pushNotificationReceived([[maybe_unused]] const std::map<std::string,
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
auto ret_token = std::make_shared<std::promise<PushNotificationResult>>();
auto future = ret_token->get_future();
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([ret_token, this, data](SecureDht&) {
if (dht_)
ret_token->set_value(dht_->pushNotificationReceived(data));
+37 -44
View File
@@ -42,8 +42,8 @@ constexpr std::string_view ORIGIN_PROTOCOL {"//"sv};
constexpr const char HTTP_HEADER_DELIM[] = "\r\n\r\n";
constexpr unsigned MAX_REDIRECTS {5};
Url::Url(std::string url_str)
: url(std::move(url_str))
Url::Url(std::string_view url_str)
: url(url_str)
{
std::string_view u = url;
if (u.empty())
@@ -239,7 +239,7 @@ Connection::~Connection()
void
Connection::close()
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
asio::error_code ec;
if (ssl_socket_) {
if (ssl_socket_->is_open())
@@ -302,13 +302,13 @@ static X509*
issuer_from_chain(STACK_OF(X509) * fullchain)
{
X509 *cert, *issuer;
const X509_NAME* issuer_name;
X509_NAME* issuer_name;
cert = cert_from_chain(fullchain);
if ((issuer_name = X509_get_issuer_name(cert)) == nullptr)
return nullptr;
issuer = X509_find_by_subject(fullchain, const_cast<X509_NAME*>(issuer_name));
issuer = X509_find_by_subject(fullchain, issuer_name);
return issuer;
}
@@ -490,7 +490,7 @@ ocspValidateResponse(const OscpRequestInfo& info,
void
Connection::set_ssl_verification(const std::string& hostname, const asio::ssl::verify_mode verify_mode)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ssl_socket_) {
// Set SNI Hostname (many hosts need this to handshake successfully)
SSL_set_tlsext_host_name(ssl_socket_->asio_ssl_stream().native_handle(), hostname.c_str());
@@ -576,7 +576,7 @@ Connection::input()
std::string
Connection::read_bytes(size_t bytes)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (bytes == 0)
bytes = read_buf_.in_avail();
std::string content;
@@ -597,7 +597,7 @@ Connection::read_until(const char delim)
void
Connection::async_connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, ConnectHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!ssl_socket_ && !socket_) {
cb(asio::error::operation_aborted, {});
return;
@@ -629,7 +629,7 @@ Connection::async_connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, Conn
void
Connection::async_handshake(HandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ssl_socket_) {
std::weak_ptr<Connection> wthis = shared_from_this();
ssl_socket_
@@ -663,7 +663,7 @@ Connection::async_handshake(HandlerCb cb)
void
Connection::async_write(BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -680,7 +680,7 @@ Connection::async_write(BytesHandlerCb cb)
void
Connection::async_read_until(const char* delim, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -697,7 +697,7 @@ Connection::async_read_until(const char* delim, BytesHandlerCb cb)
void
Connection::async_read_until(char delim, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -714,7 +714,7 @@ Connection::async_read_until(char delim, BytesHandlerCb cb)
void
Connection::async_read(size_t bytes, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -731,7 +731,7 @@ Connection::async_read(size_t bytes, BytesHandlerCb cb)
void
Connection::async_read_some(size_t bytes, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -751,7 +751,7 @@ Connection::async_read_some(size_t bytes, BytesHandlerCb cb)
void
Connection::set_keepalive(uint32_t seconds)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!ssl_socket_ && !socket_)
return;
@@ -830,8 +830,8 @@ Connection::timeout(const std::chrono::seconds& timeout, HandlerCb cb)
// Resolver
Resolver::Resolver(asio::io_context& ctx, std::string url, std::shared_ptr<dht::Logger> logger)
: url_(std::move(url))
Resolver::Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger)
: url_(url)
, resolver_(ctx)
, destroyed_(std::make_shared<bool>(false))
, logger_(logger)
@@ -871,7 +871,7 @@ Resolver::~Resolver()
{
decltype(cbs_) cbs;
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
cbs = std::move(cbs_);
}
while (not cbs.empty()) {
@@ -907,7 +907,7 @@ filter(const std::vector<asio::ip::tcp::endpoint>& epts, sa_family_t family)
void
Resolver::add_callback(ResolverCb cb, sa_family_t family)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!completed_)
cbs_.emplace(family == AF_UNSPEC ? std::move(cb)
: [cb, family](const asio::error_code& ec,
@@ -917,16 +917,8 @@ Resolver::add_callback(ResolverCb cb, sa_family_t family)
else
cb(ec, filter(endpoints, family));
});
else {
// Always dispatch the callback on the io_context thread. Invoking it
// synchronously would run the caller's continuation (e.g. the whole
// connect chain of Request::send) on the calling thread, mutating asio
// objects concurrently with the io thread running the io_context.
asio::post(resolver_.get_executor(),
[cb = std::move(cb),
ec = ec_,
endpoints = family == AF_UNSPEC ? endpoints_ : filter(endpoints_, family)] { cb(ec, endpoints); });
}
else
cb(ec_, family == AF_UNSPEC ? endpoints_ : filter(endpoints_, family));
}
void
@@ -954,7 +946,7 @@ Resolver::resolve(std::string_view host, std::string_view service)
}
decltype(cbs_) cbs;
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
ec_ = ec;
endpoints_ = std::vector<asio::ip::tcp::endpoint> {endpoints.begin(),
endpoints.end()};
@@ -975,14 +967,14 @@ Resolver::resolve(std::string_view host, std::string_view service)
std::atomic_uint Request::ids_ {1};
Request::Request(asio::io_context& ctx,
std::string url,
const std::string& url,
const Json::Value& json,
OnJsonCb jsoncb,
std::shared_ptr<dht::Logger> logger)
: logger_(std::move(logger))
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
set_header_field(restinio::http_field_t::content_type, std::string(HTTP_HEADER_CONTENT_TYPE_JSON));
@@ -1005,11 +997,11 @@ Request::Request(asio::io_context& ctx,
});
}
Request::Request(asio::io_context& ctx, std::string url, OnJsonCb jsoncb, std::shared_ptr<dht::Logger> logger)
Request::Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb, std::shared_ptr<dht::Logger> logger)
: logger_(std::move(logger))
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
set_header_field(restinio::http_field_t::accept, std::string(HTTP_HEADER_CONTENT_TYPE_JSON));
@@ -1030,20 +1022,20 @@ Request::Request(asio::io_context& ctx, std::string url, OnJsonCb jsoncb, std::s
});
}
Request::Request(asio::io_context& ctx, std::string url, std::shared_ptr<dht::Logger> logger)
Request::Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger)
: logger_(logger)
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
}
Request::Request(asio::io_context& ctx, std::string url, OnDoneCb onDone, std::shared_ptr<dht::Logger> logger)
Request::Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<dht::Logger> logger)
: logger_(logger)
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
add_on_done_callback(std::move(onDone));
@@ -1094,6 +1086,7 @@ Request::Request(asio::io_context& ctx,
, family_(family)
, resolver_(resolver)
{
set_header_field(restinio::http_field_t::host, concat(get_url().host, ":", get_url().service));
set_target(Url(target).target);
}
@@ -1397,7 +1390,7 @@ Request::connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb)
if (not sthis)
return;
auto& this_ = *sthis;
std::lock_guard lock(this_.mutex_);
std::lock_guard<std::mutex> lock(this_.mutex_);
if (ec == asio::error::operation_aborted) {
this_.terminate(ec);
return;
@@ -1450,7 +1443,7 @@ Request::send()
[wthis](const asio::error_code& ec, std::vector<asio::ip::tcp::endpoint> endpoints) {
if (auto sthis = wthis.lock()) {
auto& this_ = *sthis;
std::lock_guard lock(this_.mutex_);
std::lock_guard<std::mutex> lock(this_.mutex_);
if (ec) {
if (this_.logger_)
this_.logger_->error("[http:request:{:d}] resolve error: {:s}", this_.id_, ec.message());
@@ -1525,7 +1518,7 @@ Request::terminate(const asio::error_code& ec)
void
Request::handle_request(const asio::error_code& ec)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ec and ec != asio::error::eof) {
terminate(ec);
return;
@@ -1549,7 +1542,7 @@ Request::handle_request(const asio::error_code& ec)
void
Request::handle_response(const asio::error_code& ec, size_t /* n_bytes */)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ec && ec != asio::error::eof) {
terminate(ec);
return;
@@ -1667,11 +1660,11 @@ const Response&
Request::await()
{
std::mutex mtx;
std::unique_lock lock(mtx);
std::unique_lock<std::mutex> lock(mtx);
std::condition_variable cv;
bool ok {false};
add_on_done_callback([&](const Response&) {
std::lock_guard lk(mtx);
std::lock_guard<std::mutex> lk(mtx);
ok = true;
cv.notify_all();
});
+45 -130
View File
@@ -40,44 +40,8 @@ struct NetworkEngine::PartialMessage
time_point start;
time_point last_part;
std::unique_ptr<ParsedMessage> msg;
std::vector<std::unique_ptr<ParsedMessage>> early_parts;
size_t early_bytes {};
bool request_quota_consumed {};
};
bool
NetworkEngine::isExpectedReply(const ParsedMessage& msg, const SockAddr& from)
{
auto request = requests.find(msg.tid);
if (request != requests.end() && request->second->pending() && !request->second->node->id
&& request->second->node->getAddr() == from)
return true;
auto node = cache.getNode(msg.id, from.getFamily());
if (!node || node->getAddr() != from)
return false;
auto pending = node->getRequest(msg.tid);
return (pending && pending->pending()) || node->getSocket(msg.tid);
}
bool
NetworkEngine::tryCompleteMessage(PartialMessage& partial, const SockAddr& from)
{
try {
if (!partial.msg->complete())
return false;
process(std::move(partial.msg), from);
} catch (const std::exception& e) {
if (logger_)
logger_->warn("Error while processing partial message: {}", e.what());
} catch (...) {
if (logger_)
logger_->warn("Unknown error while processing partial message");
}
return true;
}
std::vector<Blob>
serializeValues(const std::vector<Sp<Value>>& st)
{
@@ -330,8 +294,10 @@ void
NetworkEngine::clear()
{
for (auto& request : requests) {
request.second->cancel();
request.second->node->setExpired();
if (request.second->node)
request.second->node->setExpired();
else
request.second->setExpired();
}
requests.clear();
partial_messages.clear();
@@ -502,58 +468,34 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
// partial value data
if (msg->type == MessageType::ValueData) {
if (msg->fragment_parts.empty()
|| std::any_of(msg->fragment_parts.begin(), msg->fragment_parts.end(), [](const auto& fragment) {
return fragment.second.second.empty();
})) {
auto pmsg_it = partial_messages.find(msg->tid);
if (pmsg_it == partial_messages.end()) {
if (logIncoming_)
if (logger_)
logger_->debug("Unable to find partial message");
rateLimit(from);
return;
}
PartialMessageKey key {from, msg->tid};
auto pmsg_it = partial_messages.find(key);
if (pmsg_it == partial_messages.end()) {
if (!rateLimit(from))
return;
auto inserted = partial_messages.emplace(key, PartialMessage {});
pmsg_it = inserted.first;
pmsg_it->second.from = from;
pmsg_it->second.start = now;
pmsg_it->second.last_part = now;
pmsg_it->second.request_quota_consumed = true;
scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, key));
}
auto& pmsg = pmsg_it->second;
if (!pmsg.msg) {
if (pmsg.early_parts.size() >= RX_MAX_EARLY_PARTS) {
partial_messages.erase(pmsg_it);
return;
}
size_t fragment_bytes {};
for (const auto& [index, fragment] : msg->fragment_parts) {
(void) index;
if (fragment.second.size() > RX_MAX_EARLY_BYTES - fragment_bytes) {
partial_messages.erase(pmsg_it);
return;
}
fragment_bytes += fragment.second.size();
}
if (fragment_bytes > RX_MAX_EARLY_BYTES - pmsg.early_bytes) {
partial_messages.erase(pmsg_it);
return;
}
pmsg.early_bytes += fragment_bytes;
pmsg.early_parts.emplace_back(std::move(msg));
pmsg.last_part = now;
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, key));
if (!pmsg_it->second.from.equals(from)) {
if (logger_)
logger_->debug("Received partial message data from unexpected IP address");
rateLimit(from);
return;
}
// append data block
if (pmsg.msg->append(*msg)) {
pmsg.last_part = now;
if (tryCompleteMessage(pmsg, from)) {
if (pmsg_it->second.msg->append(*msg)) {
pmsg_it->second.last_part = now;
// check data completion
if (pmsg_it->second.msg->complete()) {
try {
// process the full message
process(std::move(pmsg_it->second.msg), from);
} catch (...) {
return;
}
partial_messages.erase(pmsg_it);
} else
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, key));
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, msg->tid));
}
return;
}
@@ -564,32 +506,7 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
return;
}
if (msg->value_parts.empty()) {
if (msg->type > MessageType::Reply) {
/* Rate limit requests. */
if (!rateLimit(from)) {
if (logger_)
logger_->warn("Dropping request due to rate limiting");
return;
}
}
try {
process(std::move(msg), from);
} catch (const std::exception& e) {
if (logger_)
logger_->warn("Error while processing message: {}", e.what());
}
return;
}
PartialMessageKey partial_key {from, msg->tid};
if (msg->type == MessageType::Reply && !isExpectedReply(*msg, from))
return;
const auto partial = partial_messages.find(partial_key);
const bool request_quota_consumed = partial != partial_messages.end() && !partial->second.msg
&& partial->second.request_quota_consumed;
if (msg->type > MessageType::Reply && !request_quota_consumed) {
if (msg->type > MessageType::Reply) {
/* Rate limit requests. */
if (!rateLimit(from)) {
if (logger_)
@@ -598,28 +515,26 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
}
}
// starting partial message session
auto inserted = partial_messages.emplace(partial_key, PartialMessage {});
auto& pmsg = inserted.first->second;
if (not pmsg.msg) {
if (inserted.second) {
if (msg->value_parts.empty()) {
try {
process(std::move(msg), from);
} catch (...) {
return;
}
} else {
// starting partial message session
auto k = msg->tid;
auto& pmsg = partial_messages[k];
if (not pmsg.msg) {
pmsg.from = from;
pmsg.msg = std::move(msg);
pmsg.start = now;
scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, partial_key));
}
pmsg.msg = std::move(msg);
pmsg.last_part = now;
for (const auto& part : pmsg.early_parts)
pmsg.msg->append(*part);
pmsg.early_parts.clear();
pmsg.early_bytes = 0;
if (tryCompleteMessage(pmsg, from)) {
partial_messages.erase(inserted.first);
} else {
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, partial_key));
}
} else if (logger_)
logger_->error("Partial message from {} with given TID {} already exists", from.toString(), msg->tid);
pmsg.last_part = now;
scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, k));
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, k));
} else if (logger_)
logger_->error("Partial message with given TID {} already exists", k);
}
}
void
@@ -1698,9 +1613,9 @@ NetworkEngine::sendError(const SockAddr& addr, Tid tid, uint16_t code, const std
}
void
NetworkEngine::maintainRxBuffer(const PartialMessageKey& key)
NetworkEngine::maintainRxBuffer(Tid tid)
{
auto msg = partial_messages.find(key);
auto msg = partial_messages.find(tid);
if (msg != partial_messages.end()) {
const auto& now = scheduler.time();
if (msg->second.start + RX_MAX_PACKET_TIME < now || msg->second.last_part + RX_TIMEOUT < now) {
+8 -8
View File
@@ -9,7 +9,7 @@
#include <string>
#include <cstring>
#define close(x) closesocket(x)
#define write(s, b, f) send(s, b, (int) strlen(b), 0)
#define write(s, b, f) send(s, b, (int) (f), 0)
#define _poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
#else
#include <fcntl.h>
@@ -107,14 +107,14 @@ UdpSocket::UdpSocket(in_port_t port, const std::shared_ptr<Logger>& l)
SockAddr bind6;
bind6.setFamily(AF_INET6);
bind6.setPort(port);
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
openSockets(bind4, bind6);
}
UdpSocket::UdpSocket(const SockAddr& bind4, const SockAddr& bind6, const std::shared_ptr<Logger>& l)
: logger(l)
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
openSockets(bind4, bind6);
}
@@ -161,7 +161,7 @@ UdpSocket::sendTo(const SockAddr& dest, const uint8_t* data, size_t size, [[mayb
if (logger)
logger->d("Can't send message to %s: %s", dest.toString().c_str(), strerror(err));
if (err == EPIPE || err == ENOTCONN || err == ECONNRESET) {
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
auto bind4 = std::move(bound4), bind6 = std::move(bound6);
openSockets(bind4, bind6);
return sendTo(dest, data, size, false);
@@ -286,13 +286,13 @@ UdpSocket::openSockets(const SockAddr& bind4, const SockAddr& bind6)
pkt.received = clock::now();
onReceived(std::move(pkts));
} else if (rc == -1) {
int err = errno;
if (logger)
logger->e("Error receiving packet: %s", strerror(err));
logger->e("Error receiving packet: %s", strerror(errno));
int err = errno;
if (err == EPIPE || err == ENOTCONN || err == ECONNRESET) {
if (not running)
break;
std::unique_lock lk(lock, std::try_to_lock);
std::unique_lock<std::mutex> lk(lock, std::try_to_lock);
if (lk.owns_lock()) {
if (not running)
break;
@@ -337,7 +337,7 @@ UdpSocket::openSockets(const SockAddr& bind4, const SockAddr& bind6)
close(stop_readfd);
if (stopfd != -1)
close(stopfd);
std::unique_lock lk(lock, std::try_to_lock);
std::unique_lock<std::mutex> lk(lock, std::try_to_lock);
if (lk.owns_lock()) {
s4 = -1;
s6 = -1;
+1 -1
View File
@@ -224,7 +224,7 @@ SearchCache::expire(const time_point& now, const std::function<void(size_t)>& on
auto ret = nextExpiration_;
for (auto it = ops.begin(); it != ops.end();) {
auto expiration = it->second->getExpiration();
if (expiration <= now) {
if (expiration < now) {
auto cache = std::move(it->second);
it = ops.erase(it);
onCancel(cache->searchToken);
+6 -20
View File
@@ -59,12 +59,8 @@ unpackTid(const msgpack::object& o)
switch (o.type) {
case msgpack::type::POSITIVE_INTEGER:
return o.as<Tid>();
default: {
auto arr = o.as<std::array<char, 4>>();
uint32_t val;
std::memcpy(&val, arr.data(), sizeof(val));
return ntohl(val);
}
default:
return ntohl(*reinterpret_cast<const uint32_t*>(o.as<std::array<char, 4>>().data()));
}
}
@@ -274,21 +270,14 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
if (!parsed.a && !parsed.r && !parsed.e && !parsed.u)
throw msgpack::type_error();
auto& req = parsed.a ? *parsed.a : (parsed.r ? *parsed.r : (parsed.u ? *parsed.u : *parsed.e));
if (parsed.e) {
if (parsed.e->type != msgpack::type::ARRAY or parsed.e->via.array.size < 1)
if (parsed.e->type != msgpack::type::ARRAY)
throw msgpack::type_error();
error_code = parsed.e->via.array.ptr[0].as<uint16_t>();
}
/* An error-only message carries no request map. The error array must never
be reached through the map view of the msgpack object union, and a
request field that is not a map must be rejected rather than
reinterpreted. */
const msgpack::object* req = parsed.a ? parsed.a : (parsed.r ? parsed.r : parsed.u);
if (req and req->type != msgpack::type::MAP)
throw msgpack::type_error();
struct ParsedReq
{
msgpack::object* values;
@@ -297,9 +286,8 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
msgpack::object* want;
} parsedReq {};
const uint32_t req_size = req ? req->via.map.size : 0;
for (unsigned i = 0; i < req_size; i++) {
auto& o = req->via.map.ptr[i];
for (unsigned i = 0; i < req.via.map.size; i++) {
auto& o = req.via.map.ptr[i];
if (o.key.type != msgpack::type::STR)
continue;
auto key = o.key.as<std::string_view>();
@@ -378,8 +366,6 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
} else if (parsedReq.fields) {
if (auto rfields = findMapValue(*parsedReq.fields, "f"sv)) {
auto vfields = rfields->as<std::set<Value::Field>>();
if (vfields.empty())
throw msgpack::type_error();
if (auto rvalues = findMapValue(*parsedReq.fields, "v"sv)) {
if (rvalues->type != msgpack::type::ARRAY)
throw msgpack::type_error();
+474
View File
@@ -0,0 +1,474 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "peer_discovery.h"
#include "network_utils.h"
#include "utils.h"
#ifdef __ANDROID__
#include "compat/getif_workaround_android.h"
#endif
#include <asio.hpp>
using namespace std::literals;
namespace dht {
// Organization-local Scope multicast
constexpr char MULTICAST_ADDRESS_IPV4[] = "239.192.0.1";
constexpr char MULTICAST_ADDRESS_IPV6[] = "ff08::101";
class PeerDiscovery::DomainPeerDiscovery
{
public:
DomainPeerDiscovery(asio::ip::udp domain,
in_port_t port,
Sp<asio::io_context> ioContext = {},
Sp<Logger> logger = {});
~DomainPeerDiscovery();
void startDiscovery(std::string_view type, ServiceDiscoveredCallback callback);
void startPublish(std::string_view type, const msgpack::sbuffer& pack_buf);
void stop();
bool stopDiscovery(std::string_view type);
bool stopPublish(std::string_view type);
void connectivityChanged();
void stopConnectivityChanged();
private:
Sp<Logger> logger_;
// dmtx_ for callbackmap_ and drunning_ (write)
std::mutex dmtx_;
// mtx_ for messages_ and lrunning (listen)
std::mutex mtx_;
std::shared_ptr<asio::io_context> ioContext_;
static constexpr dht::duration PeerDiscovery_PERIOD_MAX {std::chrono::minutes(1)};
static constexpr std::chrono::seconds PeerDiscovery_PERIOD {10};
asio::steady_timer peerDiscoveryTimer;
std::chrono::steady_clock::duration peerDiscovery_period {PeerDiscovery_PERIOD};
asio::ip::udp::socket sockFd_;
asio::ip::udp::endpoint sockAddrSend_;
std::array<char, 64 * 1024> receiveBuf_;
asio::ip::udp::endpoint receiveFrom_;
msgpack::sbuffer sbuf_;
std::map<std::string, msgpack::sbuffer, std::less<>> messages_;
std::map<std::string, ServiceDiscoveredCallback, std::less<>> callbackmap_;
bool lrunning_ {false};
bool drunning_ {false};
void loopListener();
void query(const asio::ip::udp::endpoint& peer);
void reloadMessages();
void stopDiscovery();
void stopPublish();
void publish(const asio::ip::udp::endpoint& peer);
void reDiscover();
};
PeerDiscovery::DomainPeerDiscovery::DomainPeerDiscovery(asio::ip::udp domain,
in_port_t port,
Sp<asio::io_context> ioContext,
Sp<Logger> logger)
: logger_(logger)
, ioContext_(ioContext)
, peerDiscoveryTimer(*ioContext_)
, sockFd_(*ioContext_, domain)
, sockAddrSend_(asio::ip::make_address(domain.family() == AF_INET ? MULTICAST_ADDRESS_IPV4 : MULTICAST_ADDRESS_IPV6),
port)
{
try {
sockFd_.set_option(asio::ip::multicast::join_group(sockAddrSend_.address()));
sockFd_.set_option(asio::ip::udp::socket::reuse_address(true));
sockFd_.bind({domain, port});
} catch (const std::exception& e) {
#ifdef __ANDROID__
if (domain.family() == AF_INET && strcmp(e.what(), "set_option: No such device") == 0) {
try {
sockFd_.set_option(asio::ip::udp::socket::reuse_address(true));
auto mc_interface = workaround::get_interface();
sockFd_.set_option(asio::ip::multicast::outbound_interface(mc_interface));
sockFd_.set_option(asio::ip::multicast::join_group(sockAddrSend_.address().to_v4(), mc_interface));
sockFd_.bind({domain, port});
} catch (const std::exception& e) {
if (logger_)
logger_->error("Unable to start peer discovery using android workaround: {}", e.what());
}
} else
#endif
if (logger_)
logger_->error("Unable to start peer discovery for {}: {}",
domain.family() == AF_INET ? "IPv4" : "IPv6",
e.what());
}
}
PeerDiscovery::DomainPeerDiscovery::~DomainPeerDiscovery()
{
stop();
sockFd_.close();
}
void
PeerDiscovery::DomainPeerDiscovery::startDiscovery(std::string_view type, ServiceDiscoveredCallback callback)
{
std::lock_guard<std::mutex> lck(dmtx_);
callbackmap_[std::string(type)] = callback;
if (not drunning_) {
drunning_ = true;
asio::post(*ioContext_, [this]() {
loopListener();
query(sockAddrSend_);
});
}
}
void
PeerDiscovery::DomainPeerDiscovery::loopListener()
{
std::lock_guard<std::mutex> lck(dmtx_);
if (not drunning_)
return;
sockFd_.async_receive_from(asio::buffer(receiveBuf_),
receiveFrom_,
[this](const asio::error_code& error, size_t bytes) {
if (error == asio::error::operation_aborted)
return;
if (error) {
if (logger_)
logger_->error("Error receiving message: {}", error.message());
}
try {
auto rcv = msgpack::unpack(receiveBuf_.data(), bytes);
msgpack::object obj = rcv.get();
if (obj.type == msgpack::type::STR) {
if (lrunning_ and obj.as<std::string_view>() == "q"sv)
publish(receiveFrom_);
} else if (obj.type == msgpack::type::MAP) {
for (unsigned i = 0; i < obj.via.map.size; i++) {
auto& o = obj.via.map.ptr[i];
if (o.key.type != msgpack::type::STR)
continue;
ServiceDiscoveredCallback cb;
{
std::lock_guard<std::mutex> lck(dmtx_);
if (drunning_) {
auto callback = callbackmap_.find(o.key.as<std::string_view>());
if (callback != callbackmap_.end())
cb = callback->second;
} else
return;
}
if (cb)
cb(std::move(o.val),
SockAddr {receiveFrom_.data(), (socklen_t) receiveFrom_.size()});
}
} else {
throw msgpack::type_error {};
}
} catch (const std::exception& e) {
if (logger_)
logger_->error("Error receiving packet: {}", e.what());
}
loopListener();
});
}
void
PeerDiscovery::DomainPeerDiscovery::query(const asio::ip::udp::endpoint& peer)
{
std::lock_guard<std::mutex> lck(dmtx_);
if (not drunning_)
return;
msgpack::sbuffer pbuf_request;
msgpack::pack(pbuf_request, "q");
sockFd_.async_send_to(asio::buffer(pbuf_request.data(), pbuf_request.size()),
peer,
[logger = logger_, peer](const asio::error_code& ec, size_t) {
if (ec and (ec != asio::error::operation_aborted) and logger)
logger->warn("Error sending packet to: {} with err: {}",
peer.address().to_string(),
ec.message());
});
}
void
PeerDiscovery::DomainPeerDiscovery::publish(const asio::ip::udp::endpoint& peer)
{
std::lock_guard<std::mutex> lck(mtx_);
if (not lrunning_)
return;
sockFd_.async_send_to(asio::buffer((const void*) sbuf_.data(), sbuf_.size()),
peer,
[logger = logger_, peer](const asio::error_code& ec, size_t) {
if (ec and (ec != asio::error::operation_aborted) and logger)
logger->warn("Error sending packet to: {} with err: {}",
peer.address().to_string(),
ec.message());
});
}
void
PeerDiscovery::DomainPeerDiscovery::startPublish(std::string_view type, const msgpack::sbuffer& pack_buf)
{
msgpack::sbuffer pack_buf_c(pack_buf.size());
pack_buf_c.write(pack_buf.data(), pack_buf.size());
std::lock_guard<std::mutex> lck(mtx_);
messages_[std::string(type)] = std::move(pack_buf_c);
reloadMessages();
lrunning_ = true;
asio::post(*ioContext_, [this]() { publish(sockAddrSend_); });
}
bool
PeerDiscovery::DomainPeerDiscovery::stopDiscovery(std::string_view type)
{
std::lock_guard<std::mutex> lck(dmtx_);
auto it = callbackmap_.find(type);
if (it != callbackmap_.end()) {
callbackmap_.erase(it);
if (callbackmap_.empty())
stopDiscovery();
return true;
}
return false;
}
bool
PeerDiscovery::DomainPeerDiscovery::stopPublish(std::string_view type)
{
std::lock_guard<std::mutex> lck(mtx_);
auto it = messages_.find(type);
if (it != messages_.end()) {
messages_.erase(it);
if (messages_.empty())
stopPublish();
else
reloadMessages();
return true;
}
return false;
}
void
PeerDiscovery::DomainPeerDiscovery::stopDiscovery()
{
drunning_ = false;
}
void
PeerDiscovery::DomainPeerDiscovery::stopPublish()
{
lrunning_ = false;
}
void
PeerDiscovery::DomainPeerDiscovery::stop()
{
{
std::lock_guard<std::mutex> lck(dmtx_);
stopDiscovery();
}
{
std::lock_guard<std::mutex> lck(mtx_);
stopPublish();
}
}
void
PeerDiscovery::DomainPeerDiscovery::reloadMessages()
{
sbuf_.clear();
msgpack::packer<msgpack::sbuffer> pk(&sbuf_);
pk.pack_map(messages_.size());
for (const auto& m : messages_) {
pk.pack(m.first);
sbuf_.write(m.second.data(), m.second.size());
}
}
void
PeerDiscovery::DomainPeerDiscovery::reDiscover()
{
asio::error_code ec;
sockFd_.set_option(asio::ip::multicast::join_group(sockAddrSend_.address()), ec);
if (ec and logger_)
logger_->warn("Unable to multicast on {}: {}", sockAddrSend_.address().to_string(), ec.message());
query(sockAddrSend_);
}
void
PeerDiscovery::DomainPeerDiscovery::connectivityChanged()
{
asio::post(*ioContext_, [this]() {
reDiscover();
publish(sockAddrSend_);
});
if (logger_)
logger_->debug("PeerDiscovery: connectivity changed");
if (peerDiscovery_period == PeerDiscovery_PERIOD_MAX) {
peerDiscovery_period = PeerDiscovery_PERIOD;
} else {
peerDiscoveryTimer.expires_after(peerDiscovery_period);
peerDiscoveryTimer.async_wait([this](const asio::error_code& ec) {
if (ec == asio::error::operation_aborted)
return;
connectivityChanged();
});
peerDiscovery_period = std::min(peerDiscovery_period * 2, PeerDiscovery_PERIOD_MAX);
}
}
void
PeerDiscovery::DomainPeerDiscovery::stopConnectivityChanged()
{
peerDiscoveryTimer.cancel();
peerDiscovery_period = PeerDiscovery_PERIOD;
}
PeerDiscovery::PeerDiscovery(in_port_t port, Sp<asio::io_context> ioContext, Sp<Logger> logger)
{
if (not ioContext) {
ioContext = std::make_shared<asio::io_context>();
ioContext_ = ioContext;
ioRunnner_ = std::thread([logger, ioContext] {
try {
if (logger)
logger->d("[peerdiscovery] starting io_context");
auto work = asio::make_work_guard(*ioContext);
ioContext->run();
if (logger)
logger->d("[peerdiscovery] io_context stopped");
} catch (const std::exception& ex) {
if (logger)
logger->e("[peerdiscovery] run error: %s", ex.what());
}
});
}
try {
peerDiscovery4_.reset(new DomainPeerDiscovery(asio::ip::udp::v4(), port, ioContext, logger));
} catch (const std::exception& e) {
if (logger)
logger->e("[peerdiscovery] Unable to start IPv4: %s", e.what());
}
try {
peerDiscovery6_.reset(new DomainPeerDiscovery(asio::ip::udp::v6(), port, ioContext, logger));
} catch (const std::exception& e) {
if (logger)
logger->e("[peerdiscovery] Unable to start IPv6: %s", e.what());
}
}
PeerDiscovery::~PeerDiscovery()
{
stop();
if (ioContext_)
ioContext_->stop();
if (ioRunnner_.joinable())
ioRunnner_.join();
}
void
PeerDiscovery::startDiscovery(std::string_view type, ServiceDiscoveredCallback callback)
{
if (peerDiscovery4_)
peerDiscovery4_->startDiscovery(type, callback);
if (peerDiscovery6_)
peerDiscovery6_->startDiscovery(type, callback);
}
void
PeerDiscovery::startPublish(std::string_view type, const msgpack::sbuffer& pack_buf)
{
if (peerDiscovery4_)
peerDiscovery4_->startPublish(type, pack_buf);
if (peerDiscovery6_)
peerDiscovery6_->startPublish(type, pack_buf);
}
void
PeerDiscovery::startPublish(sa_family_t domain, std::string_view type, const msgpack::sbuffer& pack_buf)
{
if (domain == AF_INET) {
if (peerDiscovery4_)
peerDiscovery4_->startPublish(type, pack_buf);
} else if (domain == AF_INET6) {
if (peerDiscovery6_)
peerDiscovery6_->startPublish(type, pack_buf);
}
}
void
PeerDiscovery::stop()
{
if (peerDiscovery4_)
peerDiscovery4_->stop();
if (peerDiscovery6_)
peerDiscovery6_->stop();
}
bool
PeerDiscovery::stopDiscovery(std::string_view type)
{
bool stopped4 = peerDiscovery4_ and peerDiscovery4_->stopDiscovery(type);
bool stopped6 = peerDiscovery6_ and peerDiscovery6_->stopDiscovery(type);
return stopped4 or stopped6;
}
bool
PeerDiscovery::stopPublish(std::string_view type)
{
bool stopped4 = peerDiscovery4_ and peerDiscovery4_->stopPublish(type);
bool stopped6 = peerDiscovery6_ and peerDiscovery6_->stopPublish(type);
return stopped4 or stopped6;
}
bool
PeerDiscovery::stopPublish(sa_family_t domain, std::string_view type)
{
if (domain == AF_INET) {
return peerDiscovery4_ and peerDiscovery4_->stopPublish(type);
} else if (domain == AF_INET6) {
return peerDiscovery6_ and peerDiscovery6_->stopPublish(type);
}
return false;
}
void
PeerDiscovery::connectivityChanged()
{
if (peerDiscovery4_)
peerDiscovery4_->connectivityChanged();
if (peerDiscovery6_)
peerDiscovery6_->connectivityChanged();
}
void
PeerDiscovery::stopConnectivityChanged()
{
if (peerDiscovery4_)
peerDiscovery4_->stopConnectivityChanged();
if (peerDiscovery6_)
peerDiscovery6_->stopConnectivityChanged();
}
} /* namespace dht */
-504
View File
@@ -1,504 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dns_sd.h"
#include <algorithm>
#include <charconv>
#include <map>
namespace dht::mdns::dns_sd {
namespace {
constexpr std::string_view SERVICE_NAME = "_opendht._udp.local.";
constexpr std::string_view SERVICE_ENUMERATION_NAME = "_services._dns-sd._udp.local.";
constexpr std::string_view SERVICE_SUFFIX = "._opendht._udp.local.";
constexpr std::string_view HOST_SUFFIX = ".local.";
std::vector<uint8_t>
txtString(std::string value)
{
return {value.begin(), value.end()};
}
std::string
asciiLower(std::string_view value)
{
std::string lower;
lower.reserve(value.size());
for (const auto character : value) {
if (character >= 'A' and character <= 'Z')
lower.push_back(static_cast<char>(character + ('a' - 'A')));
else
lower.push_back(character);
}
return lower;
}
void
validateService(const Service& service)
{
if (not service.nodeId)
throw ParseError("DNS-SD service requires a node ID");
if (service.port == 0)
throw ParseError("DNS-SD service requires a port");
if (service.addresses.empty())
throw ParseError("DNS-SD service requires an address");
}
Message
makeAdvertisement(const Service& service, uint32_t serviceTtl, uint32_t hostTtl)
{
validateService(service);
const auto instance = instanceName(service.nodeId);
const auto host = hostName(service.nodeId);
Message message;
message.response = true;
message.authoritative = true;
message.answers.push_back({serviceName(), Type::PTR, CLASS_IN, false, serviceTtl, PtrData {instance}});
message.additionals.push_back({
instance, Type::SRV, CLASS_IN, true, hostTtl, SrvData {0, 0, service.port, host}
});
TxtData txt;
txt.strings.emplace_back(txtString("txtvers=1"));
txt.strings.emplace_back(txtString("id=" + service.nodeId.toString()));
txt.strings.emplace_back(txtString("net=" + std::to_string(service.network)));
message.additionals.push_back({instance, Type::TXT, CLASS_IN, true, serviceTtl, std::move(txt)});
for (const auto& address : service.addresses) {
std::visit(
[&](const auto& data) {
using Data = std::decay_t<decltype(data)>;
constexpr auto type = std::is_same_v<Data, AData> ? Type::A : Type::AAAA;
message.additionals.push_back({host, type, CLASS_IN, true, hostTtl, data});
},
address);
}
return message;
}
std::optional<InfoHash>
nodeIdFromInstance(const Name& instance)
{
const auto& labels = instance.labels();
const auto service = serviceName();
const auto& serviceLabels = service.labels();
if (labels.size() != serviceLabels.size() + 1
or not std::equal(labels.begin() + 1, labels.end(), serviceLabels.begin(), serviceLabels.end()))
return {};
if (labels.front().size() != 40)
return {};
const InfoHash nodeId {labels.front()};
if (not nodeId)
return {};
return nodeId;
}
struct TxtValues
{
InfoHash nodeId;
NetId network {};
};
std::optional<TxtValues>
parseTxt(const TxtData& txt)
{
std::map<std::string, std::string> values;
for (const auto& raw : txt.strings) {
const std::string_view string {reinterpret_cast<const char*>(raw.data()), raw.size()};
const auto separator = string.find('=');
if (separator == std::string_view::npos or separator == 0)
continue;
const auto keyView = string.substr(0, separator);
if (std::any_of(keyView.begin(), keyView.end(), [](char value) {
return value < 0x20 or value > 0x7e or value == '=';
}))
continue;
const auto key = asciiLower(keyView);
if ((key == "txtvers" or key == "id" or key == "net") and values.count(key))
return {};
values.emplace(key, std::string(string.substr(separator + 1)));
}
if (values.size() < 3 or values["txtvers"] != "1" or values["id"].size() != 40)
return {};
TxtValues parsed;
parsed.nodeId = InfoHash {values["id"]};
if (not parsed.nodeId)
return {};
const auto& network = values["net"];
const auto result = std::from_chars(network.data(), network.data() + network.size(), parsed.network);
if (result.ec != std::errc {} or result.ptr != network.data() + network.size())
return {};
return parsed;
}
template<typename Callback>
void
forEachRecord(const Message& message, Callback&& callback)
{
for (const auto& record : message.answers)
callback(record);
for (const auto& record : message.authorities)
callback(record);
for (const auto& record : message.additionals)
callback(record);
}
bool
matches(const Question& question, const ResourceRecord& record)
{
return question.name == record.name and (question.type == Type::ANY or question.type == record.type)
and (question.classCode == CLASS_ANY or question.classCode == record.classCode);
}
bool
sameRecord(const ResourceRecord& lhs, const ResourceRecord& rhs)
{
return lhs.name == rhs.name and lhs.type == rhs.type and lhs.classCode == rhs.classCode and lhs.data == rhs.data;
}
bool
isKnownAnswer(const Message& query, const ResourceRecord& record)
{
return std::any_of(query.answers.begin(), query.answers.end(), [&](const ResourceRecord& known) {
return sameRecord(known, record) and known.ttl > record.ttl / 2;
});
}
} // namespace
class Cache::Impl
{
public:
explicit Impl(Now now)
: now_(std::move(now))
{}
struct Entry
{
ResourceRecord record;
Clock::time_point expires;
};
std::vector<Service> update(const Message& message)
{
expire();
std::vector<const ResourceRecord*> records;
forEachRecord(message, [&](const ResourceRecord& record) { records.push_back(&record); });
std::vector<const ResourceRecord*> flushed;
for (const auto* record : records) {
if (not record->cacheFlush)
continue;
const auto alreadyFlushed = std::any_of(flushed.begin(), flushed.end(), [&](const ResourceRecord* other) {
return other->name == record->name and other->type == record->type
and other->classCode == record->classCode;
});
if (not alreadyFlushed) {
flushed.push_back(record);
entries_.erase(std::remove_if(entries_.begin(),
entries_.end(),
[&](const Entry& entry) {
return entry.record.name == record->name
and entry.record.type == record->type
and entry.record.classCode == record->classCode;
}),
entries_.end());
}
}
for (const auto* record : records) {
if (record->ttl == 0) {
entries_.erase(std::remove_if(entries_.begin(),
entries_.end(),
[&](const Entry& entry) { return sameRecord(entry.record, *record); }),
entries_.end());
continue;
}
const auto expires = now_() + std::chrono::seconds(record->ttl);
const auto existing = std::find_if(entries_.begin(), entries_.end(), [&](const Entry& entry) {
return sameRecord(entry.record, *record);
});
if (existing == entries_.end())
entries_.push_back({*record, expires});
else {
existing->record = *record;
existing->expires = expires;
}
}
auto current = collect();
std::vector<Service> discovered;
for (const auto& service : current) {
if (std::find_if(emitted_.begin(),
emitted_.end(),
[&](const Service& previous) { return equal(previous, service); })
== emitted_.end())
discovered.push_back(service);
}
emitted_ = std::move(current);
return discovered;
}
std::vector<Service> services()
{
expire();
auto current = collect();
emitted_ = current;
return current;
}
void clear()
{
entries_.clear();
emitted_.clear();
}
private:
static bool equal(const Service& lhs, const Service& rhs)
{
return lhs.nodeId == rhs.nodeId and lhs.network == rhs.network and lhs.port == rhs.port
and lhs.addresses == rhs.addresses;
}
void expire()
{
const auto now = now_();
entries_.erase(std::remove_if(entries_.begin(),
entries_.end(),
[&](const Entry& entry) { return entry.expires <= now; }),
entries_.end());
}
std::vector<Service> collect() const
{
std::vector<Service> result;
for (const auto& entry : entries_) {
if (entry.record.type != Type::PTR or entry.record.classCode != CLASS_IN
or entry.record.name != serviceName())
continue;
const auto* ptr = std::get_if<PtrData>(&entry.record.data);
if (not ptr)
continue;
Message message;
message.response = true;
message.answers.push_back(entry.record);
for (const auto& additional : entries_) {
if (&additional != &entry and additional.record.type != Type::PTR)
message.additionals.push_back(additional.record);
}
const auto service = resolve(message);
if (service and std::none_of(result.begin(), result.end(), [&](const Service& existing) {
return equal(existing, *service);
}))
result.push_back(*service);
}
return result;
}
Now now_;
std::vector<Entry> entries_;
std::vector<Service> emitted_;
};
Cache::Cache(Now now)
: impl_(std::make_unique<Impl>(std::move(now)))
{}
Cache::~Cache() = default;
Cache::Cache(Cache&&) noexcept = default;
Cache& Cache::operator=(Cache&&) noexcept = default;
std::vector<Service>
Cache::update(const Message& message)
{
return impl_->update(message);
}
std::vector<Service>
Cache::services()
{
return impl_->services();
}
void
Cache::clear()
{
impl_->clear();
}
Name
serviceName()
{
return Name::fromString(SERVICE_NAME);
}
Name
instanceName(const InfoHash& nodeId)
{
if (not nodeId)
throw ParseError("DNS-SD instance requires a node ID");
return Name::fromString(nodeId.toString() + std::string(SERVICE_SUFFIX));
}
Name
hostName(const InfoHash& nodeId)
{
if (not nodeId)
throw ParseError("mDNS host requires a node ID");
return Name::fromString(nodeId.toString() + std::string(HOST_SUFFIX));
}
Message
announcement(const Service& service)
{
return makeAdvertisement(service, SERVICE_RECORD_TTL, HOST_RECORD_TTL);
}
Message
goodbye(const Service& service)
{
return makeAdvertisement(service, 0, 0);
}
Message
browseQuery()
{
Message query;
query.questions.push_back({serviceName(), Type::PTR, CLASS_IN, false});
return query;
}
std::optional<Message>
respond(const Message& query, const Service& service)
{
if (query.response)
return {};
const auto advertisement = announcement(service);
Message response;
response.id = query.id;
response.response = true;
response.authoritative = true;
const ResourceRecord serviceType {
Name::fromString(SERVICE_ENUMERATION_NAME),
Type::PTR,
CLASS_IN,
false,
SERVICE_RECORD_TTL,
PtrData {serviceName()},
};
auto addMatching = [&](const ResourceRecord& record) {
const auto question = std::find_if(query.questions.begin(), query.questions.end(), [&](const Question& q) {
return matches(q, record);
});
if (question == query.questions.end() or (not question->unicastResponse and isKnownAnswer(query, record)))
return;
if (std::find_if(response.answers.begin(),
response.answers.end(),
[&](const ResourceRecord& answer) { return sameRecord(answer, record); })
== response.answers.end())
response.answers.push_back(record);
};
addMatching(serviceType);
for (const auto& record : advertisement.answers)
addMatching(record);
for (const auto& record : advertisement.additionals)
addMatching(record);
if (response.answers.empty())
return {};
const auto metaOnly = std::all_of(response.answers.begin(), response.answers.end(), [&](const auto& answer) {
return sameRecord(answer, serviceType);
});
if (metaOnly)
return response;
response.additionals = advertisement.additionals;
response.additionals.erase(std::remove_if(response.additionals.begin(),
response.additionals.end(),
[&](const ResourceRecord& record) {
return std::any_of(response.answers.begin(),
response.answers.end(),
[&](const ResourceRecord& answer) {
return sameRecord(answer, record);
});
}),
response.additionals.end());
return response;
}
std::optional<Service>
resolve(const Message& message)
{
std::vector<Name> instances;
forEachRecord(message, [&](const ResourceRecord& record) {
if (record.type == Type::PTR and record.classCode == CLASS_IN and record.name == serviceName()) {
if (const auto ptr = std::get_if<PtrData>(&record.data))
instances.push_back(ptr->name);
}
});
if (instances.size() != 1)
return {};
const auto nodeId = nodeIdFromInstance(instances.front());
if (not nodeId)
return {};
const auto expectedHost = hostName(*nodeId);
const SrvData* srv = nullptr;
const TxtData* txt = nullptr;
std::vector<AddressData> addresses;
auto duplicate = false;
forEachRecord(message, [&](const ResourceRecord& record) {
if (record.classCode != CLASS_IN)
return;
if (record.name == instances.front()) {
if (record.type == Type::SRV) {
if (srv)
duplicate = true;
else
srv = std::get_if<SrvData>(&record.data);
} else if (record.type == Type::TXT) {
if (txt)
duplicate = true;
else
txt = std::get_if<TxtData>(&record.data);
}
} else if (record.name == expectedHost) {
if (record.type == Type::A) {
if (const auto data = std::get_if<AData>(&record.data))
addresses.emplace_back(*data);
} else if (record.type == Type::AAAA) {
if (const auto data = std::get_if<AaaaData>(&record.data))
addresses.emplace_back(*data);
}
}
});
if (duplicate or not srv or not txt or srv->priority != 0 or srv->weight != 0 or srv->port == 0
or srv->target != expectedHost or addresses.empty())
return {};
const auto txtValues = parseTxt(*txt);
if (not txtValues or txtValues->nodeId != *nodeId)
return {};
Service service;
service.nodeId = *nodeId;
service.network = txtValues->network;
service.port = srv->port;
for (auto& address : addresses) {
if (std::find(service.addresses.begin(), service.addresses.end(), address) == service.addresses.end())
service.addresses.emplace_back(std::move(address));
}
return service;
}
} // namespace dht::mdns::dns_sd
-61
View File
@@ -1,61 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include "mdns.h"
#include "opendht/infohash.h"
#include "opendht/utils.h"
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
namespace dht::mdns::dns_sd {
constexpr uint32_t HOST_RECORD_TTL = 120;
constexpr uint32_t SERVICE_RECORD_TTL = 4500;
using AddressData = std::variant<AData, AaaaData>;
struct Service
{
InfoHash nodeId;
NetId network {};
in_port_t port {};
std::vector<AddressData> addresses;
};
class OPENDHT_PUBLIC Cache
{
public:
using Clock = std::chrono::steady_clock;
using Now = std::function<Clock::time_point()>;
explicit Cache(Now now = [] { return Clock::now(); });
~Cache();
Cache(Cache&&) noexcept;
Cache& operator=(Cache&&) noexcept;
std::vector<Service> update(const Message& message);
std::vector<Service> services();
void clear();
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
OPENDHT_PUBLIC Name serviceName();
OPENDHT_PUBLIC Name instanceName(const InfoHash& nodeId);
OPENDHT_PUBLIC Name hostName(const InfoHash& nodeId);
OPENDHT_PUBLIC Message announcement(const Service& service);
OPENDHT_PUBLIC Message goodbye(const Service& service);
OPENDHT_PUBLIC Message browseQuery();
OPENDHT_PUBLIC std::optional<Message> respond(const Message& query, const Service& service);
OPENDHT_PUBLIC std::optional<Service> resolve(const Message& message);
} // namespace dht::mdns::dns_sd
-563
View File
@@ -1,563 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "mdns.h"
#include <algorithm>
#include <cctype>
#include <limits>
#include <map>
#include <unordered_map>
namespace dht::mdns {
namespace {
constexpr uint16_t FLAG_RESPONSE = 0x8000;
constexpr uint16_t FLAG_AUTHORITATIVE = 0x0400;
constexpr uint16_t FLAG_TRUNCATED = 0x0200;
constexpr uint16_t CLASS_TOP_BIT = 0x8000;
constexpr uint16_t CLASS_MASK = 0x7fff;
char
asciiLower(char value)
{
const auto byte = static_cast<unsigned char>(value);
return static_cast<char>(std::tolower(byte));
}
void
validateLabels(const std::vector<std::string>& labels)
{
size_t wireSize = 0;
for (const auto& label : labels) {
if (label.empty() or label.size() > 63)
throw ParseError("invalid DNS label length");
wireSize += label.size() + 1;
if (wireSize > 255)
throw ParseError("DNS name is too long");
}
}
std::string
suffixKey(const std::vector<std::string>& labels, size_t first)
{
std::string key;
for (auto index = first; index < labels.size(); ++index) {
key.push_back(static_cast<char>(labels[index].size()));
key.append(labels[index]);
}
key.push_back('\0');
return key;
}
class Writer
{
public:
void write8(uint8_t value)
{
ensure(1);
packet_.push_back(value);
}
void write16(uint16_t value)
{
ensure(2);
packet_.push_back(static_cast<uint8_t>(value >> 8));
packet_.push_back(static_cast<uint8_t>(value));
}
void write32(uint32_t value)
{
write16(static_cast<uint16_t>(value >> 16));
write16(static_cast<uint16_t>(value));
}
template<size_t Size>
void write(const std::array<uint8_t, Size>& bytes)
{
write(bytes.data(), bytes.size());
}
void write(const uint8_t* bytes, size_t size)
{
ensure(size);
packet_.insert(packet_.end(), bytes, bytes + size);
}
void writeName(const Name& name)
{
const auto& labels = name.labels();
for (size_t index = 0; index < labels.size(); ++index) {
const auto key = suffixKey(labels, index);
if (const auto found = suffixes_.find(key); found != suffixes_.end()) {
write16(static_cast<uint16_t>(0xc000 | found->second));
return;
}
if (packet_.size() < 0x4000)
suffixes_.emplace(key, static_cast<uint16_t>(packet_.size()));
write8(static_cast<uint8_t>(labels[index].size()));
write(reinterpret_cast<const uint8_t*>(labels[index].data()), labels[index].size());
}
write8(0);
}
size_t size() const noexcept { return packet_.size(); }
void patch16(size_t offset, uint16_t value)
{
if (offset + 2 > packet_.size())
throw ParseError("invalid DNS packet patch offset");
packet_[offset] = static_cast<uint8_t>(value >> 8);
packet_[offset + 1] = static_cast<uint8_t>(value);
}
Packet take() { return std::move(packet_); }
private:
void ensure(size_t additional) const
{
if (additional > MAX_PACKET_SIZE - packet_.size())
throw ParseError("mDNS packet exceeds maximum size");
}
Packet packet_;
std::unordered_map<std::string, uint16_t> suffixes_;
};
class Reader
{
public:
Reader(const uint8_t* packet, size_t size)
: packet_(packet)
, size_(size)
{}
uint8_t read8()
{
require(1);
return packet_[position_++];
}
uint16_t read16()
{
const auto high = read8();
const auto low = read8();
return static_cast<uint16_t>((high << 8) | low);
}
uint32_t read32() { return (static_cast<uint32_t>(read16()) << 16) | read16(); }
Name readName()
{
std::vector<std::string> labels;
std::vector<size_t> visited;
auto cursor = position_;
auto jumped = false;
size_t wireSize = 0;
while (true) {
if (cursor >= size_)
throw ParseError("truncated DNS name");
if (std::find(visited.begin(), visited.end(), cursor) != visited.end())
throw ParseError("DNS compression pointer loop");
visited.push_back(cursor);
const auto length = packet_[cursor];
if ((length & 0xc0) == 0xc0) {
if (cursor + 1 >= size_)
throw ParseError("truncated DNS compression pointer");
const auto pointer = static_cast<size_t>(((length & 0x3f) << 8) | packet_[cursor + 1]);
if (pointer >= cursor)
throw ParseError("DNS compression pointer does not point backwards");
if (not jumped)
position_ = cursor + 2;
cursor = pointer;
jumped = true;
continue;
}
if (length & 0xc0)
throw ParseError("invalid DNS label prefix");
++cursor;
if (length == 0) {
if (not jumped)
position_ = cursor;
break;
}
if (length > 63 or length > size_ - cursor)
throw ParseError("invalid or truncated DNS label");
wireSize += length + 1;
if (wireSize > 255)
throw ParseError("DNS name is too long");
labels.emplace_back(reinterpret_cast<const char*>(packet_ + cursor), length);
cursor += length;
if (not jumped)
position_ = cursor;
}
return Name {std::move(labels)};
}
std::vector<uint8_t> readBytes(size_t size)
{
require(size);
std::vector<uint8_t> bytes(packet_ + position_, packet_ + position_ + size);
position_ += size;
return bytes;
}
size_t position() const noexcept { return position_; }
void setPosition(size_t position)
{
if (position > size_)
throw ParseError("invalid DNS packet position");
position_ = position;
}
private:
void require(size_t count) const
{
if (count > size_ - position_)
throw ParseError("truncated DNS packet");
}
const uint8_t* packet_;
size_t size_;
size_t position_ {};
};
uint16_t
checkedCount(size_t count)
{
if (count > std::numeric_limits<uint16_t>::max())
throw ParseError("too many DNS records");
return static_cast<uint16_t>(count);
}
void
encodeQuestion(Writer& writer, const Question& question)
{
writer.writeName(question.name);
writer.write16(static_cast<uint16_t>(question.type));
writer.write16(static_cast<uint16_t>(question.classCode | (question.unicastResponse ? CLASS_TOP_BIT : 0)));
}
void
encodeNsec(Writer& writer, const NsecData& nsec)
{
writer.writeName(nsec.nextDomain);
std::map<uint8_t, std::array<uint8_t, 32>> windows;
std::map<uint8_t, uint8_t> lengths;
for (const auto type : nsec.types) {
const auto value = static_cast<uint16_t>(type);
const auto window = static_cast<uint8_t>(value >> 8);
const auto low = static_cast<uint8_t>(value);
const auto byte = static_cast<uint8_t>(low / 8);
const auto bit = static_cast<uint8_t>(low % 8);
windows[window][byte] |= static_cast<uint8_t>(1u << (7 - bit));
lengths[window] = std::max<uint8_t>(lengths[window], byte + 1);
}
for (const auto& [window, bitmap] : windows) {
writer.write8(window);
writer.write8(lengths[window]);
writer.write(bitmap.data(), lengths[window]);
}
}
void
encodeRecord(Writer& writer, const ResourceRecord& record)
{
writer.writeName(record.name);
writer.write16(static_cast<uint16_t>(record.type));
writer.write16(static_cast<uint16_t>(record.classCode | (record.cacheFlush ? CLASS_TOP_BIT : 0)));
writer.write32(record.ttl);
const auto lengthOffset = writer.size();
writer.write16(0);
const auto dataOffset = writer.size();
switch (record.type) {
case Type::A:
writer.write(std::get<AData>(record.data).address);
break;
case Type::AAAA:
writer.write(std::get<AaaaData>(record.data).address);
break;
case Type::PTR:
writer.writeName(std::get<PtrData>(record.data).name);
break;
case Type::TXT:
for (const auto& string : std::get<TxtData>(record.data).strings) {
if (string.size() > 255)
throw ParseError("DNS TXT string is too long");
writer.write8(static_cast<uint8_t>(string.size()));
writer.write(string.data(), string.size());
}
break;
case Type::SRV: {
const auto& srv = std::get<SrvData>(record.data);
writer.write16(srv.priority);
writer.write16(srv.weight);
writer.write16(srv.port);
writer.writeName(srv.target);
break;
}
case Type::NSEC:
encodeNsec(writer, std::get<NsecData>(record.data));
break;
default: {
const auto& bytes = std::get<UnknownData>(record.data).bytes;
writer.write(bytes.data(), bytes.size());
break;
}
}
const auto dataSize = writer.size() - dataOffset;
if (dataSize > std::numeric_limits<uint16_t>::max())
throw ParseError("DNS record data is too large");
writer.patch16(lengthOffset, static_cast<uint16_t>(dataSize));
}
Question
decodeQuestion(Reader& reader)
{
Question question;
question.name = reader.readName();
question.type = static_cast<Type>(reader.read16());
const auto classField = reader.read16();
question.classCode = classField & CLASS_MASK;
question.unicastResponse = classField & CLASS_TOP_BIT;
return question;
}
NsecData
decodeNsec(Reader& reader, size_t end)
{
NsecData nsec;
nsec.nextDomain = reader.readName();
while (reader.position() < end) {
const auto window = reader.read8();
const auto length = reader.read8();
if (length == 0 or length > 32 or length > end - reader.position())
throw ParseError("invalid DNS NSEC bitmap");
const auto bitmap = reader.readBytes(length);
for (size_t byte = 0; byte < bitmap.size(); ++byte) {
for (uint8_t bit = 0; bit < 8; ++bit) {
if (bitmap[byte] & (1u << (7 - bit))) {
const auto value = static_cast<uint16_t>((window << 8) | (byte * 8 + bit));
nsec.types.push_back(static_cast<Type>(value));
}
}
}
}
return nsec;
}
ResourceRecord
decodeRecord(Reader& reader)
{
ResourceRecord record;
record.name = reader.readName();
record.type = static_cast<Type>(reader.read16());
const auto classField = reader.read16();
record.classCode = classField & CLASS_MASK;
record.cacheFlush = classField & CLASS_TOP_BIT;
record.ttl = reader.read32();
const auto dataLength = reader.read16();
const auto dataStart = reader.position();
const auto dataEnd = dataStart + dataLength;
if (dataEnd < dataStart)
throw ParseError("invalid DNS record length");
switch (record.type) {
case Type::A: {
if (dataLength != 4)
throw ParseError("invalid DNS A record length");
AData data;
const auto bytes = reader.readBytes(data.address.size());
std::copy(bytes.begin(), bytes.end(), data.address.begin());
record.data = data;
break;
}
case Type::AAAA: {
if (dataLength != 16)
throw ParseError("invalid DNS AAAA record length");
AaaaData data;
const auto bytes = reader.readBytes(data.address.size());
std::copy(bytes.begin(), bytes.end(), data.address.begin());
record.data = data;
break;
}
case Type::PTR:
record.data = PtrData {reader.readName()};
break;
case Type::TXT: {
TxtData data;
while (reader.position() < dataEnd) {
const auto length = reader.read8();
if (length > dataEnd - reader.position())
throw ParseError("truncated DNS TXT string");
data.strings.push_back(reader.readBytes(length));
}
record.data = std::move(data);
break;
}
case Type::SRV: {
if (dataLength < 7)
throw ParseError("invalid DNS SRV record length");
SrvData data;
data.priority = reader.read16();
data.weight = reader.read16();
data.port = reader.read16();
data.target = reader.readName();
record.data = std::move(data);
break;
}
case Type::NSEC:
record.data = decodeNsec(reader, dataEnd);
break;
default:
record.data = UnknownData {reader.readBytes(dataLength)};
break;
}
if (reader.position() != dataEnd)
throw ParseError("DNS record data length mismatch");
return record;
}
} // namespace
Name::Name(std::vector<std::string> labels)
: labels_(std::move(labels))
{
validateLabels(labels_);
}
Name
Name::fromString(std::string_view name)
{
if (name.empty() or name == ".")
return {};
if (name.back() == '.')
name.remove_suffix(1);
std::vector<std::string> labels;
size_t start = 0;
while (start <= name.size()) {
const auto end = name.find('.', start);
const auto length = (end == std::string_view::npos ? name.size() : end) - start;
if (length == 0)
throw ParseError("empty DNS label");
labels.emplace_back(name.substr(start, length));
if (end == std::string_view::npos)
break;
start = end + 1;
}
return Name {std::move(labels)};
}
std::string
Name::toString() const
{
if (labels_.empty())
return ".";
std::string name;
for (const auto& label : labels_) {
name.append(label);
name.push_back('.');
}
return name;
}
bool
operator==(const Name& lhs, const Name& rhs) noexcept
{
if (lhs.labels_.size() != rhs.labels_.size())
return false;
for (size_t index = 0; index < lhs.labels_.size(); ++index) {
const auto& left = lhs.labels_[index];
const auto& right = rhs.labels_[index];
if (left.size() != right.size())
return false;
if (not std::equal(left.begin(), left.end(), right.begin(), [](char a, char b) {
return asciiLower(a) == asciiLower(b);
}))
return false;
}
return true;
}
Packet
encode(const Message& message)
{
const auto recordCount = message.questions.size() + message.answers.size() + message.authorities.size()
+ message.additionals.size();
if (recordCount > MAX_RECORD_COUNT)
throw ParseError("too many DNS records");
Writer writer;
writer.write16(message.id);
uint16_t flags = 0;
if (message.response)
flags |= FLAG_RESPONSE;
if (message.authoritative)
flags |= FLAG_AUTHORITATIVE;
if (message.truncated)
flags |= FLAG_TRUNCATED;
writer.write16(flags);
writer.write16(checkedCount(message.questions.size()));
writer.write16(checkedCount(message.answers.size()));
writer.write16(checkedCount(message.authorities.size()));
writer.write16(checkedCount(message.additionals.size()));
for (const auto& question : message.questions)
encodeQuestion(writer, question);
for (const auto& record : message.answers)
encodeRecord(writer, record);
for (const auto& record : message.authorities)
encodeRecord(writer, record);
for (const auto& record : message.additionals)
encodeRecord(writer, record);
return writer.take();
}
Message
decode(const uint8_t* packet, size_t size)
{
if (packet == nullptr)
throw ParseError("null DNS packet");
if (size > MAX_PACKET_SIZE)
throw ParseError("mDNS packet exceeds maximum size");
Reader reader(packet, size);
Message message;
message.id = reader.read16();
const auto flags = reader.read16();
message.response = flags & FLAG_RESPONSE;
message.authoritative = flags & FLAG_AUTHORITATIVE;
message.truncated = flags & FLAG_TRUNCATED;
const auto questionCount = reader.read16();
const auto answerCount = reader.read16();
const auto authorityCount = reader.read16();
const auto additionalCount = reader.read16();
const auto recordCount = static_cast<size_t>(questionCount) + answerCount + authorityCount + additionalCount;
if (recordCount > MAX_RECORD_COUNT)
throw ParseError("too many DNS records");
message.questions.reserve(questionCount);
message.answers.reserve(answerCount);
message.authorities.reserve(authorityCount);
message.additionals.reserve(additionalCount);
for (size_t index = 0; index < questionCount; ++index)
message.questions.push_back(decodeQuestion(reader));
for (size_t index = 0; index < answerCount; ++index)
message.answers.push_back(decodeRecord(reader));
for (size_t index = 0; index < authorityCount; ++index)
message.authorities.push_back(decodeRecord(reader));
for (size_t index = 0; index < additionalCount; ++index)
message.additionals.push_back(decodeRecord(reader));
if (reader.position() != size)
throw ParseError("trailing bytes in DNS packet");
return message;
}
} // namespace dht::mdns
-194
View File
@@ -1,194 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include "opendht/def.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
namespace dht::mdns {
constexpr size_t MAX_PACKET_SIZE = 9000;
constexpr size_t MAX_RECORD_COUNT = 1024;
constexpr uint16_t CLASS_IN = 1;
constexpr uint16_t CLASS_ANY = 255;
enum class Type : uint16_t {
A = 1,
PTR = 12,
TXT = 16,
AAAA = 28,
SRV = 33,
NSEC = 47,
ANY = 255,
};
class OPENDHT_PUBLIC ParseError : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
class OPENDHT_PUBLIC Name
{
public:
Name() = default;
explicit Name(std::vector<std::string> labels);
static Name fromString(std::string_view name);
const std::vector<std::string>& labels() const noexcept { return labels_; }
std::string toString() const;
friend bool operator==(const Name& lhs, const Name& rhs) noexcept;
friend bool operator!=(const Name& lhs, const Name& rhs) noexcept { return not(lhs == rhs); }
private:
std::vector<std::string> labels_;
};
OPENDHT_PUBLIC bool operator==(const Name& lhs, const Name& rhs) noexcept;
struct Question
{
Name name;
Type type {Type::ANY};
uint16_t classCode {CLASS_IN};
bool unicastResponse {false};
};
struct AData
{
std::array<uint8_t, 4> address {};
};
struct AaaaData
{
std::array<uint8_t, 16> address {};
};
struct PtrData
{
Name name;
};
struct TxtData
{
std::vector<std::vector<uint8_t>> strings;
};
struct SrvData
{
uint16_t priority {};
uint16_t weight {};
uint16_t port {};
Name target;
};
struct NsecData
{
Name nextDomain;
std::vector<Type> types;
};
struct UnknownData
{
std::vector<uint8_t> bytes;
};
using RData = std::variant<AData, AaaaData, PtrData, TxtData, SrvData, NsecData, UnknownData>;
struct ResourceRecord
{
Name name;
Type type {Type::ANY};
uint16_t classCode {CLASS_IN};
bool cacheFlush {false};
uint32_t ttl {};
RData data {UnknownData {}};
};
struct Message
{
uint16_t id {};
bool response {false};
bool authoritative {false};
bool truncated {false};
std::vector<Question> questions;
std::vector<ResourceRecord> answers;
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additionals;
};
using Packet = std::vector<uint8_t>;
OPENDHT_PUBLIC Packet encode(const Message& message);
OPENDHT_PUBLIC Message decode(const uint8_t* packet, size_t size);
inline bool
operator==(const Question& lhs, const Question& rhs)
{
return lhs.name == rhs.name and lhs.type == rhs.type and lhs.classCode == rhs.classCode
and lhs.unicastResponse == rhs.unicastResponse;
}
inline bool
operator==(const AData& lhs, const AData& rhs)
{
return lhs.address == rhs.address;
}
inline bool
operator==(const AaaaData& lhs, const AaaaData& rhs)
{
return lhs.address == rhs.address;
}
inline bool
operator==(const PtrData& lhs, const PtrData& rhs)
{
return lhs.name == rhs.name;
}
inline bool
operator==(const TxtData& lhs, const TxtData& rhs)
{
return lhs.strings == rhs.strings;
}
inline bool
operator==(const SrvData& lhs, const SrvData& rhs)
{
return lhs.priority == rhs.priority and lhs.weight == rhs.weight and lhs.port == rhs.port
and lhs.target == rhs.target;
}
inline bool
operator==(const NsecData& lhs, const NsecData& rhs)
{
return lhs.nextDomain == rhs.nextDomain and lhs.types == rhs.types;
}
inline bool
operator==(const UnknownData& lhs, const UnknownData& rhs)
{
return lhs.bytes == rhs.bytes;
}
inline bool
operator==(const ResourceRecord& lhs, const ResourceRecord& rhs)
{
return lhs.name == rhs.name and lhs.type == rhs.type and lhs.classCode == rhs.classCode
and lhs.cacheFlush == rhs.cacheFlush and lhs.ttl == rhs.ttl and lhs.data == rhs.data;
}
inline bool
operator==(const Message& lhs, const Message& rhs)
{
return lhs.id == rhs.id and lhs.response == rhs.response and lhs.authoritative == rhs.authoritative
and lhs.truncated == rhs.truncated and lhs.questions == rhs.questions and lhs.answers == rhs.answers
and lhs.authorities == rhs.authorities and lhs.additionals == rhs.additionals;
}
} // namespace dht::mdns
-455
View File
@@ -1,455 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "mdns_transport.h"
#include <asio.hpp>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstring>
#include <future>
#include <mutex>
#ifdef _WIN32
#include <iphlpapi.h>
#else
#include <ifaddrs.h>
#include <net/if.h>
#endif
namespace dht::mdns {
namespace {
constexpr in_port_t MDNS_PORT = 5353;
constexpr std::string_view MDNS_IPV4 = "224.0.0.251";
constexpr std::string_view MDNS_IPV6 = "ff02::fb";
std::vector<InterfaceAddress>
interfaceAddresses([[maybe_unused]] asio::io_context& ioContext)
{
std::vector<InterfaceAddress> result;
#ifdef _WIN32
ULONG size = 15000;
std::vector<uint8_t> buffer(size);
auto* adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
auto status = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
nullptr,
adapters,
&size);
if (status == ERROR_BUFFER_OVERFLOW) {
buffer.resize(size);
adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
status = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
nullptr,
adapters,
&size);
}
if (status == NO_ERROR) {
InterfaceId id = 0;
for (auto* adapter = adapters; adapter; adapter = adapter->Next) {
if (adapter->OperStatus != IfOperStatusUp)
continue;
++id;
const auto multicast = not(adapter->Flags & IP_ADAPTER_NO_MULTICAST);
const auto loopback = adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK;
for (auto* current = adapter->FirstUnicastAddress; current; current = current->Next) {
const auto* socketAddress = current->Address.lpSockaddr;
if (socketAddress->sa_family == AF_INET) {
const auto* address = reinterpret_cast<const sockaddr_in*>(socketAddress);
asio::ip::address_v4::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin_addr, bytes.size());
result.push_back({id, adapter->IfIndex, asio::ip::address_v4(bytes), multicast, loopback});
} else if (socketAddress->sa_family == AF_INET6) {
const auto* address = reinterpret_cast<const sockaddr_in6*>(socketAddress);
asio::ip::address_v6::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin6_addr, bytes.size());
result.push_back({id,
adapter->Ipv6IfIndex,
asio::ip::address_v6(bytes, address->sin6_scope_id),
multicast,
loopback});
}
}
}
}
#else
ifaddrs* addresses = nullptr;
if (getifaddrs(&addresses) != 0)
return result;
for (auto* current = addresses; current; current = current->ifa_next) {
if (not current->ifa_addr or not(current->ifa_flags & IFF_UP))
continue;
const auto family = current->ifa_addr->sa_family;
if (family == AF_INET) {
const auto* address = reinterpret_cast<const sockaddr_in*>(current->ifa_addr);
asio::ip::address_v4::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin_addr, bytes.size());
const auto index = if_nametoindex(current->ifa_name);
result.push_back({index,
index,
asio::ip::address_v4(bytes),
bool(current->ifa_flags & IFF_MULTICAST),
bool(current->ifa_flags & IFF_LOOPBACK)});
} else if (family == AF_INET6) {
const auto* address = reinterpret_cast<const sockaddr_in6*>(current->ifa_addr);
asio::ip::address_v6::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin6_addr, bytes.size());
const auto index = if_nametoindex(current->ifa_name);
result.push_back({index,
index,
asio::ip::address_v6(bytes, address->sin6_scope_id),
bool(current->ifa_flags & IFF_MULTICAST),
bool(current->ifa_flags & IFF_LOOPBACK)});
}
}
freeifaddrs(addresses);
#endif
return result;
}
} // namespace
std::vector<NetworkInterface>
groupInterfaces(const std::vector<InterfaceAddress>& addresses)
{
std::vector<NetworkInterface> result;
const auto hasNonLoopback = std::any_of(addresses.begin(), addresses.end(), [](const InterfaceAddress& address) {
return address.multicast and not address.loopback and not address.address.is_unspecified()
and not address.address.is_multicast();
});
for (const auto& address : addresses) {
if (address.id == 0 or not address.multicast or (hasNonLoopback and address.loopback)
or address.address.is_unspecified() or address.address.is_multicast())
continue;
auto scope = std::find_if(result.begin(), result.end(), [&](const NetworkInterface& interface) {
return interface.id == address.id;
});
if (scope == result.end()) {
NetworkInterface interface;
interface.id = address.id;
result.push_back(std::move(interface));
scope = std::prev(result.end());
}
dns_sd::AddressData record;
if (address.address.is_v4()) {
scope->ipv4Index = address.index;
if (not scope->ipv4Outbound)
scope->ipv4Outbound = address.address.to_v4();
record = AData {address.address.to_v4().to_bytes()};
} else {
const auto bytes = address.address.to_v6().to_bytes();
const auto mapped = std::all_of(bytes.begin(), bytes.begin() + 10, [](uint8_t byte) { return byte == 0; })
and bytes[10] == 0xff and bytes[11] == 0xff;
if (mapped)
continue;
scope->ipv6Index = address.index;
record = AaaaData {bytes};
}
if (std::find(scope->addresses.begin(), scope->addresses.end(), record) == scope->addresses.end())
scope->addresses.emplace_back(std::move(record));
}
result.erase(std::remove_if(result.begin(),
result.end(),
[](const NetworkInterface& interface) { return interface.addresses.empty(); }),
result.end());
return result;
}
class MdnsTransport::Impl : public std::enable_shared_from_this<MdnsTransport::Impl>
{
public:
Impl(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
: ioContext_(std::move(ioContext))
, logger_(std::move(logger))
, interfaces_(groupInterfaces(interfaceAddresses(*ioContext_)))
{}
struct Socket
{
explicit Socket(asio::io_context& ioContext)
: socket(ioContext)
{}
asio::ip::udp::socket socket;
std::array<uint8_t, MAX_PACKET_SIZE> receiveBuffer {};
asio::ip::udp::endpoint sender;
asio::ip::udp::endpoint multicast;
InterfaceId interface {};
unsigned ipv6Index {};
};
void start(ReceiveCallback callback)
{
callback_ = std::move(callback);
configure();
}
void send(Packet packet) { send(std::move(packet), {}, {}); }
void send(Packet packet, std::optional<asio::ip::udp::endpoint> destination, std::optional<InterfaceId> interface)
{
auto sharedPacket = std::make_shared<Packet>(std::move(packet));
asio::post(*ioContext_, [weak = weak_from_this(), sharedPacket, destination, interface] {
const auto self = weak.lock();
if (not self)
return;
for (const auto& state : self->sockets_) {
if (interface and state->interface != *interface)
continue;
const auto target = destination.value_or(state->multicast);
if (target.address().is_v4() != state->multicast.address().is_v4())
continue;
state->socket.async_send_to(asio::buffer(*sharedPacket),
target,
[logger = self->logger_, sharedPacket](const asio::error_code& error,
size_t) {
if (error and error != asio::error::operation_aborted and logger)
logger->warn("Unable to send mDNS packet: {}", error.message());
});
}
});
}
void sendAndWait(Packet packet, InterfaceId interface)
{
if (ioContext_->get_executor().running_in_this_thread()) {
sendNow(packet, interface);
return;
}
auto completed = std::make_shared<std::promise<void>>();
auto future = completed->get_future();
asio::post(*ioContext_, [weak = weak_from_this(), packet = std::move(packet), interface, completed] {
if (const auto self = weak.lock())
self->sendNow(packet, interface);
completed->set_value();
});
future.wait_for(std::chrono::milliseconds(250));
}
void refresh()
{
{
std::lock_guard lock(interfacesMutex_);
interfaces_ = groupInterfaces(interfaceAddresses(*ioContext_));
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->configure();
});
}
void stop()
{
if (ioContext_->get_executor().running_in_this_thread()) {
stopNow();
return;
}
auto completed = std::make_shared<std::promise<void>>();
auto future = completed->get_future();
asio::post(*ioContext_, [weak = weak_from_this(), completed] {
if (const auto self = weak.lock())
self->stopNow();
completed->set_value();
});
future.wait_for(std::chrono::milliseconds(250));
}
std::vector<NetworkInterface> interfaces() const
{
std::lock_guard lock(interfacesMutex_);
return interfaces_;
}
private:
void sendNow(const Packet& packet, InterfaceId interface)
{
for (const auto& state : sockets_) {
if (state->interface != interface)
continue;
asio::error_code error;
state->socket.send_to(asio::buffer(packet), state->multicast, 0, error);
if (error and logger_)
logger_->warn("Unable to send final mDNS packet: {}", error.message());
}
}
void stopNow()
{
callback_ = {};
stopSockets();
}
void configure()
{
stopSockets();
std::vector<NetworkInterface> currentInterfaces;
{
std::lock_guard lock(interfacesMutex_);
currentInterfaces = interfaces_;
}
for (const auto& interface : currentInterfaces) {
if (interface.ipv4Outbound)
configure(interface, asio::ip::udp::v4(), asio::ip::make_address(MDNS_IPV4));
if (interface.ipv6Index)
configure(interface, asio::ip::udp::v6(), asio::ip::make_address(MDNS_IPV6));
}
}
void configure(const NetworkInterface& interface, asio::ip::udp protocol, const asio::ip::address& multicast)
{
auto state = std::make_shared<Socket>(*ioContext_);
asio::error_code error;
state->socket.open(protocol, error);
if (error)
return logSocketError(protocol, error);
state->socket.set_option(asio::ip::udp::socket::reuse_address(true), error);
#if defined(SO_REUSEPORT) && !defined(_WIN32)
if (not error) {
const int enabled = 1;
if (::setsockopt(state->socket.native_handle(), SOL_SOCKET, SO_REUSEPORT, &enabled, sizeof(enabled)) != 0)
error.assign(errno, asio::error::get_system_category());
}
#endif
if (error)
return logSocketError(protocol, error);
if (protocol == asio::ip::udp::v6())
state->socket.set_option(asio::ip::v6_only(true), error);
state->socket.bind({protocol, MDNS_PORT}, error);
if (error)
return logSocketError(protocol, error);
state->socket.set_option(asio::ip::multicast::hops(255), error);
if (error)
return logSocketError(protocol, error);
state->socket.set_option(asio::ip::multicast::enable_loopback(true), error);
if (protocol == asio::ip::udp::v4()) {
state->socket.set_option(asio::ip::multicast::join_group(multicast.to_v4(), *interface.ipv4Outbound), error);
if (not error)
state->socket.set_option(asio::ip::multicast::outbound_interface(*interface.ipv4Outbound), error);
} else {
state->socket.set_option(asio::ip::multicast::join_group(multicast.to_v6(), interface.ipv6Index), error);
if (not error)
state->socket.set_option(asio::ip::multicast::outbound_interface(interface.ipv6Index), error);
}
if (error)
return logSocketError(protocol, error);
if (protocol == asio::ip::udp::v6())
state->multicast = {asio::ip::address_v6(multicast.to_v6().to_bytes(), interface.ipv6Index), MDNS_PORT};
else
state->multicast = {multicast, MDNS_PORT};
state->interface = interface.id;
state->ipv6Index = interface.ipv6Index;
sockets_.push_back(state);
receive(state);
}
void receive(const std::shared_ptr<Socket>& state)
{
state->socket.async_receive_from(asio::buffer(state->receiveBuffer),
state->sender,
[weak = weak_from_this(), state](const asio::error_code& error, size_t size) {
const auto self = weak.lock();
if (not self)
return;
if (not error and self->callback_) {
Packet packet(state->receiveBuffer.begin(),
state->receiveBuffer.begin() + size);
self->callback_(packet,
{state->sender, state->interface, state->ipv6Index});
} else if (error != asio::error::operation_aborted and self->logger_) {
self->logger_->warn("Unable to receive mDNS packet: {}",
error.message());
}
if (state->socket.is_open())
self->receive(state);
});
}
void stopSockets()
{
for (const auto& state : sockets_) {
asio::error_code error;
state->socket.cancel(error);
state->socket.close(error);
}
sockets_.clear();
}
void logSocketError(asio::ip::udp protocol, const asio::error_code& error)
{
if (logger_)
logger_->warn("Unable to start {} mDNS transport: {}",
protocol == asio::ip::udp::v4() ? "IPv4" : "IPv6",
error.message());
}
std::shared_ptr<asio::io_context> ioContext_;
std::shared_ptr<Logger> logger_;
mutable std::mutex interfacesMutex_;
std::vector<NetworkInterface> interfaces_;
std::vector<std::shared_ptr<Socket>> sockets_;
ReceiveCallback callback_;
};
MdnsTransport::MdnsTransport(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
: impl_(std::make_shared<Impl>(std::move(ioContext), std::move(logger)))
{}
MdnsTransport::~MdnsTransport() = default;
void
MdnsTransport::start(ReceiveCallback callback)
{
impl_->start(std::move(callback));
}
void
MdnsTransport::send(Packet packet)
{
impl_->send(std::move(packet));
}
void
MdnsTransport::send(Packet packet, InterfaceId interface)
{
impl_->send(std::move(packet), {}, interface);
}
void
MdnsTransport::send(Packet packet, const asio::ip::udp::endpoint& destination, InterfaceId interface)
{
impl_->send(std::move(packet), destination, interface);
}
void
MdnsTransport::sendAndWait(Packet packet, InterfaceId interface)
{
impl_->sendAndWait(std::move(packet), interface);
}
void
MdnsTransport::refresh()
{
impl_->refresh();
}
void
MdnsTransport::stop()
{
if (impl_)
impl_->stop();
}
std::vector<NetworkInterface>
MdnsTransport::interfaces() const
{
return impl_->interfaces();
}
} // namespace dht::mdns
-74
View File
@@ -1,74 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include "dns_sd.h"
#include "opendht/logger.h"
#include <asio/ip/address.hpp>
#include <asio/ip/udp.hpp>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
namespace asio {
class io_context;
}
namespace dht::mdns {
using InterfaceId = uint64_t;
struct InterfaceAddress
{
InterfaceId id {};
unsigned index {};
asio::ip::address address;
bool multicast {true};
bool loopback {false};
};
struct NetworkInterface
{
InterfaceId id {};
unsigned ipv4Index {};
unsigned ipv6Index {};
std::optional<asio::ip::address_v4> ipv4Outbound;
std::vector<dns_sd::AddressData> addresses;
};
OPENDHT_PUBLIC std::vector<NetworkInterface> groupInterfaces(const std::vector<InterfaceAddress>& addresses);
struct ReceiveContext
{
asio::ip::udp::endpoint source;
InterfaceId interface {};
unsigned ipv6Index {};
};
class MdnsTransport
{
public:
using ReceiveCallback = std::function<void(const Packet&, const ReceiveContext&)>;
MdnsTransport(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger = {});
~MdnsTransport();
void start(ReceiveCallback callback);
void send(Packet packet);
void send(Packet packet, InterfaceId interface);
void send(Packet packet, const asio::ip::udp::endpoint& destination, InterfaceId interface);
void sendAndWait(Packet packet, InterfaceId interface);
void refresh();
void stop();
std::vector<NetworkInterface> interfaces() const;
private:
class Impl;
std::shared_ptr<Impl> impl_;
};
} // namespace dht::mdns
-438
View File
@@ -1,438 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "peer_discovery.h"
#include "dns_sd.h"
#include "mdns_transport.h"
#include <asio.hpp>
#include <chrono>
#include <cstring>
#include <map>
#include <mutex>
#include <optional>
#include <random>
namespace dht {
namespace {
constexpr auto BROWSE_INTERVAL = std::chrono::seconds(60);
constexpr auto CONNECTIVITY_RETRY = std::chrono::seconds(10);
constexpr auto CONNECTIVITY_RETRY_MAX = std::chrono::minutes(1);
SockAddr
socketAddress(const mdns::dns_sd::AddressData& data, in_port_t port, const mdns::ReceiveContext& context)
{
SockAddr address;
std::visit(
[&](const auto& record) {
using Record = std::decay_t<decltype(record)>;
if constexpr (std::is_same_v<Record, mdns::AData>) {
address.setFamily(AF_INET);
std::memcpy(&address.getIPv4().sin_addr, record.address.data(), record.address.size());
} else {
address.setFamily(AF_INET6);
std::memcpy(&address.getIPv6().sin6_addr, record.address.data(), record.address.size());
const asio::ip::address_v6 parsed(record.address);
if (parsed.is_link_local())
address.getIPv6().sin6_scope_id = context.ipv6Index;
}
},
data);
address.setPort(port);
return address;
}
} // namespace
class PeerDiscovery::Impl : public std::enable_shared_from_this<PeerDiscovery::Impl>
{
public:
Impl(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
: ioContext_(std::move(ioContext))
, logger_(std::move(logger))
, transport_(ioContext_, logger_)
, browseTimer_(*ioContext_)
, announceTimer_(*ioContext_)
, connectivityTimer_(*ioContext_)
{}
void start()
{
transport_.start([weak = weak_from_this()](const mdns::Packet& packet, const mdns::ReceiveContext& context) {
if (const auto self = weak.lock())
self->receive(packet, context);
});
if (logger_)
logger_->debug("[peer discovery {:p}] Started.", fmt::ptr(this));
}
void startDiscovery(NetId network, PeerDiscoveredCallback callback)
{
{
std::lock_guard lock(mutex_);
discoveryNetwork_ = network;
discoveryCallback_ = std::move(callback);
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->browse();
});
}
void startPublish(const InfoHash& nodeId, NetId network, in_port_t port)
{
mdns::dns_sd::Service service {nodeId, network, port, {}};
if (transport_.interfaces().empty())
throw mdns::ParseError("DNS-SD service requires a multicast interface");
{
std::lock_guard lock(mutex_);
published_ = std::move(service);
}
announce();
asio::post(*ioContext_, [weak = weak_from_this()] {
const auto self = weak.lock();
if (not self)
return;
self->announceTimer_.expires_after(std::chrono::seconds(1));
self->announceTimer_.async_wait([weak](const asio::error_code& error) {
if (not error) {
if (const auto locked = weak.lock())
locked->announce();
}
});
});
}
bool stopDiscovery()
{
std::lock_guard lock(mutex_);
const auto stopped = bool(discoveryCallback_);
discoveryCallback_ = {};
discoveryNetwork_.reset();
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->browseTimer_.cancel();
});
return stopped;
}
bool stopPublish()
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = std::exchange(published_, {});
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->announceTimer_.cancel();
});
if (service)
advertise(*service, true, false);
return bool(service);
}
void stop()
{
stopConnectivityChanged();
stopDiscovery();
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = std::exchange(published_, {});
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->announceTimer_.cancel();
});
if (service)
advertise(*service, true, true);
transport_.stop();
}
void connectivityChanged()
{
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->handleConnectivityChanged();
});
}
void stopConnectivityChanged()
{
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock()) {
self->connectivityTimer_.cancel();
self->connectivityRetry_ = CONNECTIVITY_RETRY;
}
});
}
private:
void handleConnectivityChanged()
{
caches_.clear();
transport_.refresh();
refreshPublication();
browse();
connectivityTimer_.expires_after(connectivityRetry_);
connectivityTimer_.async_wait([weak = weak_from_this()](const asio::error_code& error) {
if (not error) {
if (const auto self = weak.lock())
self->handleConnectivityChanged();
}
});
connectivityRetry_ = std::min(connectivityRetry_ * 2,
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
CONNECTIVITY_RETRY_MAX));
}
void browse()
{
bool active;
{
std::lock_guard lock(mutex_);
active = bool(discoveryCallback_);
}
if (not active)
return;
if (logger_)
logger_->debug("[peer discovery {:p}] Browsing.", fmt::ptr(this));
transport_.send(mdns::encode(mdns::dns_sd::browseQuery()));
browseTimer_.expires_after(BROWSE_INTERVAL);
browseTimer_.async_wait([weak = weak_from_this()](const asio::error_code& error) {
if (not error) {
if (const auto self = weak.lock())
self->browse();
}
});
}
void announce()
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = published_;
}
if (logger_)
logger_->debug("[peer discovery {:p}] Announcing.", fmt::ptr(this));
if (service)
advertise(*service, false, false);
}
void refreshPublication()
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
if (published_)
service = published_;
}
if (service)
advertise(*service, false, false);
}
void advertise(const mdns::dns_sd::Service& service, bool goodbye, bool wait)
{
for (const auto& interface : transport_.interfaces()) {
auto scoped = service;
scoped.addresses = interface.addresses;
auto packet = mdns::encode(goodbye ? mdns::dns_sd::goodbye(scoped) : mdns::dns_sd::announcement(scoped));
if (wait)
transport_.sendAndWait(std::move(packet), interface.id);
else
transport_.send(std::move(packet), interface.id);
}
}
void receive(const mdns::Packet& packet, const mdns::ReceiveContext& context)
{
if (logger_)
logger_->debug("[peer discovery {:p}] Got packet of size {}.", fmt::ptr(this), packet.size());
try {
const auto message = mdns::decode(packet.data(), packet.size());
if (message.response)
discover(message, context);
else
answer(message, context);
} catch (const std::exception& error) {
if (logger_)
logger_->warn("Ignoring invalid mDNS packet: {}", error.what());
}
}
void answer(const mdns::Message& query, const mdns::ReceiveContext& context)
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = published_;
}
if (service) {
const auto interfaces = transport_.interfaces();
const auto interface = std::find_if(interfaces.begin(), interfaces.end(), [&](const auto& candidate) {
return candidate.id == context.interface;
});
if (interface == interfaces.end())
return;
service->addresses = interface->addresses;
if (auto response = mdns::dns_sd::respond(query, *service)) {
const auto unicast = context.source.port() != 5353
or std::any_of(query.questions.begin(),
query.questions.end(),
[](const mdns::Question& question) {
return question.unicastResponse;
});
if (context.source.port() != 5353)
response->questions = query.questions;
auto packet = mdns::encode(*response);
const auto sharedAnswer = std::any_of(response->answers.begin(),
response->answers.end(),
[](const mdns::ResourceRecord& record) {
return not record.cacheFlush;
});
if (not sharedAnswer) {
if (unicast)
transport_.send(std::move(packet), context.source, context.interface);
else
transport_.send(std::move(packet), context.interface);
return;
}
static thread_local std::mt19937 random {std::random_device {}()};
const auto delay = std::chrono::milliseconds(std::uniform_int_distribution<int> {20, 120}(random));
auto timer = std::make_shared<asio::steady_timer>(*ioContext_, delay);
timer->async_wait([weak = weak_from_this(), timer, packet = std::move(packet), context, unicast](
const asio::error_code& error) mutable {
if (error)
return;
const auto self = weak.lock();
if (not self)
return;
if (unicast)
self->transport_.send(std::move(packet), context.source, context.interface);
else
self->transport_.send(std::move(packet), context.interface);
});
}
}
}
void discover(const mdns::Message& message, const mdns::ReceiveContext& context)
{
PeerDiscoveredCallback callback;
std::optional<NetId> network;
std::optional<InfoHash> localNode;
{
std::lock_guard lock(mutex_);
callback = discoveryCallback_;
network = discoveryNetwork_;
if (published_)
localNode = published_->nodeId;
}
if (not callback or not network)
return;
const auto cache = caches_.try_emplace(context.interface).first;
for (const auto& service : cache->second.update(message)) {
if (service.network != *network or (localNode and service.nodeId == *localNode))
continue;
for (const auto& address : service.addresses)
callback(service.nodeId, socketAddress(address, service.port, context));
}
}
std::shared_ptr<asio::io_context> ioContext_;
std::shared_ptr<Logger> logger_;
mdns::MdnsTransport transport_;
asio::steady_timer browseTimer_;
asio::steady_timer announceTimer_;
asio::steady_timer connectivityTimer_;
std::chrono::steady_clock::duration connectivityRetry_ {CONNECTIVITY_RETRY};
std::mutex mutex_;
std::optional<NetId> discoveryNetwork_;
PeerDiscoveredCallback discoveryCallback_;
std::optional<mdns::dns_sd::Service> published_;
std::map<mdns::InterfaceId, mdns::dns_sd::Cache> caches_;
};
PeerDiscovery::PeerDiscovery(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
{
if (not ioContext) {
ioContext = std::make_shared<asio::io_context>();
ioContext_ = ioContext;
}
impl_ = std::make_shared<Impl>(ioContext, logger);
impl_->start();
if (ioContext_) {
ioRunner_ = std::thread([logger = std::move(logger), ioContext = std::move(ioContext)] {
try {
auto work = asio::make_work_guard(*ioContext);
ioContext->run();
} catch (const std::exception& error) {
if (logger)
logger->error("Peer discovery event loop failed: {}", error.what());
}
});
}
}
PeerDiscovery::~PeerDiscovery()
{
stop();
impl_.reset();
if (ioContext_)
ioContext_->stop();
if (ioRunner_.joinable())
ioRunner_.join();
}
void
PeerDiscovery::startDiscovery(NetId network, PeerDiscoveredCallback callback)
{
impl_->startDiscovery(network, std::move(callback));
}
void
PeerDiscovery::startPublish(const InfoHash& nodeId, NetId network, in_port_t port)
{
impl_->startPublish(nodeId, network, port);
}
bool
PeerDiscovery::stopDiscovery()
{
return impl_->stopDiscovery();
}
bool
PeerDiscovery::stopPublish()
{
return impl_->stopPublish();
}
void
PeerDiscovery::stop()
{
if (impl_)
impl_->stop();
}
void
PeerDiscovery::connectivityChanged()
{
impl_->connectivityChanged();
}
void
PeerDiscovery::stopConnectivityChanged()
{
impl_->stopConnectivityChanged();
}
} // namespace dht
-10
View File
@@ -133,16 +133,6 @@ struct Dht::SearchNode
return last_get_reply + Node::NODE_EXPIRE_TIME;
}
/**
* Update the token without refreshing last_get_reply,
* so that periodic find_node/get requests still occur.
*/
void refreshToken(const Blob& tok)
{
if (not tok.empty() and tok.size() <= 64)
token = tok;
}
friend std::ostream& operator<<(std::ostream& s, const SearchNode& node)
{
s << "getStatus:" << node.getStatus.size() << " listenStatus:" << node.listenStatus.size()
+141 -9
View File
@@ -96,13 +96,37 @@ SecureDht::secureType(ValueType&& type)
return type;
}
Sp<crypto::Certificate>
SecureDht::getCertificate(const InfoHash& node) const
{
if (node == getId())
return certificate_;
auto it = nodesCertificates_.find(node);
if (it == nodesCertificates_.end())
return nullptr;
else
return it->second;
}
Sp<crypto::PublicKey>
SecureDht::getPublicKey(const InfoHash& node) const
{
if (node == getId())
return certificate_->getSharedPublicKey();
auto it = nodesPubKeys_.find(node);
if (it == nodesPubKeys_.end())
return nullptr;
else
return it->second;
}
Sp<crypto::Certificate>
SecureDht::getCertificate(const PkId& node) const
{
if (node == getLongId())
return certificate_;
auto it = nodesCertificates_.find(node);
if (it == nodesCertificates_.end())
auto it = nodesCertificatesLong_.find(node);
if (it == nodesCertificatesLong_.end())
return nullptr;
else
return it->second;
@@ -113,8 +137,8 @@ SecureDht::getPublicKey(const PkId& node) const
{
if (node == getLongId())
return certificate_->getSharedPublicKey();
auto it = nodesPubKeys_.find(node);
if (it == nodesPubKeys_.end())
auto it = nodesPubKeysLong_.find(node);
if (it == nodesPubKeysLong_.end())
return nullptr;
else
return it->second;
@@ -167,10 +191,85 @@ void
SecureDht::registerCertificate(const Sp<crypto::Certificate>& cert)
{
if (cert) {
nodesCertificates_[cert->getLongId()] = cert;
nodesCertificates_[cert->getId()] = cert;
nodesCertificatesLong_[cert->getLongId()] = cert;
}
}
void
SecureDht::findCertificate(const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb)
{
Sp<crypto::Certificate> b = getCertificate(node);
if (b && *b) {
if (logger_)
logger_->debug("Using certificate from cache for {}", node.toString());
if (cb)
cb(b);
return;
}
if (localQueryMethodLegacy_) {
auto res = localQueryMethodLegacy_(node);
if (not res.empty()) {
if (logger_)
logger_->debug("Registering certificate from local store for {}", node.toString());
nodesCertificates_.emplace(node, res.front());
nodesCertificatesLong_.emplace(res.front()->getLongId(), res.front());
if (cb)
cb(res.front());
return;
}
}
auto found = std::make_shared<bool>(false);
dht_->get(
node,
[cb, node, found, this](const std::vector<Sp<Value>>& vals) {
for (const auto& v : vals) {
if (auto cert = registerCertificate(node, v->data)) {
*found = true;
if (logger_)
logger_->debug("Found certificate for {}", node.toString());
if (cb)
cb(cert);
return false;
}
}
return !*found;
},
[cb, found](bool) {
if (!*found and cb)
cb(nullptr);
},
Value::TypeFilter(CERTIFICATE_TYPE));
}
void
SecureDht::findPublicKey(const InfoHash& node, const std::function<void(const Sp<crypto::PublicKey>)>& cb)
{
auto pk = getPublicKey(node);
if (pk && *pk) {
if (logger_)
logger_->debug("Found public key from cache for {}", node.toString());
if (cb)
cb(pk);
return;
}
findCertificate(node, [=](const Sp<crypto::Certificate>& crt) {
if (crt && *crt) {
auto pk = crt->getSharedPublicKey();
if (*pk) {
nodesPubKeys_[pk->getId()] = pk;
nodesPubKeysLong_[pk->getLongId()] = pk;
if (cb)
cb(pk);
return;
}
}
if (cb)
cb(nullptr);
});
}
void
SecureDht::findCertificate(const PkId& node, const std::function<void(const Sp<crypto::Certificate>)>& cb)
{
@@ -188,7 +287,8 @@ SecureDht::findCertificate(const PkId& node, const std::function<void(const Sp<c
if (not res.empty()) {
if (logger_)
logger_->debug("Registering certificate from local store for {}", node.to_c_str());
nodesCertificates_.emplace(node, res.front());
nodesCertificates_.emplace(res.front()->getId(), res.front());
nodesCertificatesLong_.emplace(node, res.front());
if (cb)
cb(res.front());
return;
@@ -233,7 +333,8 @@ SecureDht::findPublicKey(const PkId& node, const std::function<void(const Sp<cry
if (crt && *crt) {
auto pk = crt->getSharedPublicKey();
if (*pk) {
nodesPubKeys_[pk->getLongId()] = pk;
nodesPubKeys_[pk->getId()] = pk;
nodesPubKeysLong_[pk->getLongId()] = pk;
if (cb)
cb(pk);
return;
@@ -261,7 +362,8 @@ SecureDht::checkValue(const Sp<Value>& v)
if (auto decrypted_val = v->decrypt(*key_)) {
auto cacheValue = not isDecrypted and decrypted_val->owner;
if (cacheValue) {
nodesPubKeys_[decrypted_val->owner->getLongId()] = decrypted_val->owner;
nodesPubKeys_[decrypted_val->owner->getId()] = decrypted_val->owner;
nodesPubKeysLong_[decrypted_val->owner->getLongId()] = decrypted_val->owner;
}
return decrypted_val;
}
@@ -275,7 +377,8 @@ SecureDht::checkValue(const Sp<Value>& v)
auto cacheValue = not v->isSignatureChecked() and enableCache_ and v->owner;
if (v->checkSignature()) {
if (cacheValue) {
nodesPubKeys_[v->owner->getLongId()] = v->owner;
nodesPubKeys_[v->owner->getId()] = v->owner;
nodesPubKeysLong_[v->owner->getLongId()] = v->owner;
}
return v;
} else if (logger_)
@@ -389,6 +492,35 @@ SecureDht::putSigned(const InfoHash& hash, Sp<Value> val, DoneCallback callback,
Where().id(val->id));
}
void
SecureDht::putEncrypted(const InfoHash& hash, const InfoHash& to, Sp<Value> val, DoneCallback callback, bool permanent)
{
if (not key_) {
if (callback)
callback(false, {});
return;
}
findPublicKey(to,
[this, hash, val = std::move(val), callback = std::move(callback), permanent](
const Sp<crypto::PublicKey>& pk) {
if (!pk || !*pk) {
if (callback)
callback(false, {});
return;
}
if (logger_)
logger_->warn("Encrypting data for PK: {}", pk->getId().toString());
try {
dht_->put(hash, encrypt(*val, *pk), callback, time_point::max(), permanent);
} catch (const std::exception& e) {
if (logger_)
logger_->error("Error putting encrypted data: {}", e.what());
if (callback)
callback(false, {});
}
});
}
void
SecureDht::putEncrypted(const InfoHash& hash, const PkId& to, Sp<Value> val, DoneCallback callback, bool permanent)
{
+3 -9
View File
@@ -6,10 +6,8 @@
#include <atomic>
#include <thread>
#include <iostream>
#include <cmath> // std::pow
#if __cplusplus < 202002L
#include <ciso646>
#endif
#include <ciso646> // fix windows compiler bug
#include <cmath> // std::pow
namespace dht {
@@ -80,8 +78,6 @@ ThreadPool::run(std::function<void()>&& cb)
break;
task = std::move(tasks_.front());
tasks_.pop();
if (tasks_.empty() && stopping_)
stopCv_.notify_one();
}
// run task
@@ -139,9 +135,7 @@ ThreadPool::stop(bool wait)
{
std::unique_lock l(lock_);
if (wait) {
stopping_ = true;
stopCv_.wait(l, [&]() { return tasks_.empty(); });
stopping_ = false;
cv_.wait(l, [&]() { return tasks_.empty(); });
}
running_ = false;
tasks_ = {};
+14 -44
View File
@@ -238,58 +238,28 @@ operator==(const SockAddr& a, const SockAddr& b)
return a.equals(b);
}
time_point
from_system_time(system_clock::time_point t, time_point steady_now, system_clock::time_point system_now)
{
if (t == system_clock::time_point::min())
return time_point::min();
if (t == system_clock::time_point::max())
return time_point::max();
auto dt = t - system_now;
if (dt > system_clock::duration(0) and steady_now > time_point::max() - dt)
return time_point::max();
else if (dt < system_clock::duration(0) and steady_now < time_point::min() - dt)
return time_point::min();
return steady_now + dt;
}
time_point
from_system_time(system_clock::time_point t)
{
return from_system_time(t, clock::now(), system_clock::now());
}
system_clock::time_point
to_system_time(time_point t, time_point steady_now, system_clock::time_point system_now)
{
if (t == time_point::min())
return system_clock::time_point::min();
if (t == time_point::max())
return system_clock::time_point::max();
auto dt = t - steady_now;
if (dt > duration(0) and system_now >= system_clock::time_point::max() - dt)
return system_clock::time_point::max();
else if (dt < duration(0) and system_now <= system_clock::time_point::min() - dt)
return system_clock::time_point::min();
return system_now + std::chrono::duration_cast<system_clock::duration>(dt);
}
system_clock::time_point
to_system_time(time_point t)
{
return to_system_time(t, clock::now(), system_clock::now());
}
time_point
from_time_t(std::time_t t)
{
return from_system_time(system_clock::from_time_t(t));
auto dt = system_clock::from_time_t(t) - system_clock::now();
auto now = clock::now();
if (dt > system_clock::duration(0) and now > time_point::max() - dt)
return time_point::max();
else if (dt < system_clock::duration(0) and now < time_point::min() - dt)
return time_point::min();
return now + dt;
}
std::time_t
to_time_t(time_point t)
{
return system_clock::to_time_t(to_system_time(t));
auto dt = t - clock::now();
auto now = system_clock::now();
if (dt > duration(0) and now >= system_clock::time_point::max() - dt)
return system_clock::to_time_t(system_clock::time_point::max());
else if (dt < duration(0) and now <= system_clock::time_point::min() - dt)
return system_clock::to_time_t(system_clock::time_point::min());
return system_clock::to_time_t(now + std::chrono::duration_cast<system_clock::duration>(dt));
}
Blob
+1 -12
View File
@@ -85,7 +85,7 @@ ValueType::DEFAULT_STORE_POLICY(InfoHash, const std::shared_ptr<Value>& v, const
size_t
Value::size() const
{
return cypher.size() + data.size() + signature.size() + user_type.size() + pushType.size();
return cypher.size() + data.size() + signature.size() + user_type.size();
}
void
@@ -104,14 +104,9 @@ Value::msgpack_unpack(const msgpack::object& o)
} else
throw msgpack::type_error();
priority = 0;
pushType.clear();
if (auto rprio = findMapValue(o, VALUE_KEY_PRIO)) {
priority = rprio->as<unsigned>();
}
if (auto rpt = findMapValue(o, VALUE_KEY_PUSHTYPE)) {
pushType = rpt->as<std::string>();
}
}
void
@@ -202,9 +197,6 @@ Value::Value(const Json::Value& json)
const auto& jprio = json["prio"];
if (jprio.isIntegral())
priority = jprio.asUInt();
const auto& jpt = json[VALUE_KEY_PUSHTYPE];
if (jpt.isString())
pushType = jpt.asString();
}
Json::Value
@@ -231,8 +223,6 @@ Value::toJson() const
}
if (priority)
val["prio"] = priority;
if (not pushType.empty())
val[VALUE_KEY_PUSHTYPE] = pushType;
return val;
}
@@ -271,7 +261,6 @@ Value::encrypt(const crypto::PrivateKey& from, const crypto::PublicKey& to)
sign(from);
Value nv {id};
nv.priority = priority;
nv.pushType = pushType;
nv.setCypher(to.encrypt(getToEncrypt()));
return nv;
}
+2 -178
View File
@@ -59,24 +59,6 @@ CryptoTester::testSignatureEncryption()
}
}
void
CryptoTester::testPublicKeyPemIsPlainText()
{
auto key = dht::crypto::PrivateKey::generate();
auto pem = key.getPublicKey().toString();
// The exported key is PEM text: it must not carry the terminating null
// byte GnuTLS counts in the exported size, which would otherwise be
// embedded in anything the string is serialized into.
CPPUNIT_ASSERT(not pem.empty());
CPPUNIT_ASSERT_EQUAL(std::string::npos, pem.find('\0'));
CPPUNIT_ASSERT_EQUAL('\n', pem.back());
// The exported text still reads back as the same key.
dht::crypto::PublicKey reloaded(dht::Blob(pem.begin(), pem.end()));
CPPUNIT_ASSERT_EQUAL(key.getPublicKey().getId(), reloaded.getId());
}
void
CryptoTester::testCertificateRevocation()
{
@@ -113,159 +95,10 @@ CryptoTester::testCertificateRevocation()
CPPUNIT_ASSERT_MESSAGE(v.toString(), v);
}
namespace {
/** Read a CRL Number extension blob (big-endian, variable length) as an integer. */
uint64_t
crlNumberValue(const dht::Blob& number)
{
uint64_t value {0};
for (uint8_t byte : number)
value = (value << 8) | byte;
return value;
}
} // namespace
void
CryptoTester::testRevocationListNumber()
{
auto ca = dht::crypto::generateIdentity("ca");
auto device = dht::crypto::generateIdentity("dev", ca);
dht::crypto::RevocationList crl;
CPPUNIT_ASSERT_EQUAL(uint64_t(0), crlNumberValue(crl.getNumber()));
crl.revoke(*device.second);
crl.sign(ca);
auto first = crl.getNumber();
// An unnumbered list gets a random number on its first signature.
CPPUNIT_ASSERT(not first.empty());
CPPUNIT_ASSERT(crlNumberValue(first) != 0);
// Re-signing keeps the same lineage and moves the number forward.
crl.sign(ca);
auto second = crl.getNumber();
CPPUNIT_ASSERT_EQUAL(crlNumberValue(first) + 1, crlNumberValue(second));
crl.sign(ca);
CPPUNIT_ASSERT_EQUAL(crlNumberValue(first) + 2, crlNumberValue(crl.getNumber()));
// The number is a DER integer, so it is stored with leading zeros stripped.
// Incrementing must still work for numbers shorter than 8 bytes.
for (uint64_t start : {uint64_t(1), uint64_t(0x7f), uint64_t(0x80), uint64_t(0xffff)}) {
dht::crypto::RevocationList small;
small.revoke(*device.second);
small.sign(ca, {}, start);
CPPUNIT_ASSERT_EQUAL(start, crlNumberValue(small.getNumber()));
small.sign(ca);
CPPUNIT_ASSERT_EQUAL(start + 1, crlNumberValue(small.getNumber()));
small.sign(ca);
CPPUNIT_ASSERT_EQUAL(start + 2, crlNumberValue(small.getNumber()));
}
}
void
CryptoTester::testRevocationListExplicitNumber()
{
auto ca = dht::crypto::generateIdentity("ca");
auto device = dht::crypto::generateIdentity("dev", ca);
// Values chosen to cover leading zero bytes, a full-width number and the largest
// value that still encodes as a positive 8-byte DER integer.
const std::vector<uint64_t> numbers {1,
42,
0xff,
0x100,
0x0000'0001'0000'0000ull,
0x0000'02a1'0000'0001ull,
0x7fff'ffff'ffff'ffffull};
for (auto number : numbers) {
dht::crypto::RevocationList crl;
crl.revoke(*device.second);
crl.sign(ca, {}, number);
CPPUNIT_ASSERT_EQUAL(number, crlNumberValue(crl.getNumber()));
CPPUNIT_ASSERT(crl.isSignedBy(*ca.second));
CPPUNIT_ASSERT(crl.isRevoked(*device.second));
// The number must survive a pack/unpack round-trip.
dht::crypto::RevocationList unpacked(crl.getPacked());
CPPUNIT_ASSERT_EQUAL(number, crlNumberValue(unpacked.getNumber()));
}
// An explicit number overrides the increment, in both directions.
dht::crypto::RevocationList crl;
crl.revoke(*device.second);
crl.sign(ca, {}, 1000);
CPPUNIT_ASSERT_EQUAL(uint64_t(1000), crlNumberValue(crl.getNumber()));
crl.sign(ca, {}, 5);
CPPUNIT_ASSERT_EQUAL(uint64_t(5), crlNumberValue(crl.getNumber()));
// Passing 0 falls back to the default behavior: increment the current number.
crl.sign(ca, {}, 0);
CPPUNIT_ASSERT_EQUAL(uint64_t(6), crlNumberValue(crl.getNumber()));
}
void
CryptoTester::testRevocationListPartitionedNumber()
{
// Two devices holding the same authority issue revocation lists independently.
// RFC 5280 5.2.3 requires CRL numbers to increase monotonically for a given CRL
// scope and issuer; partitioning the low bits by device keeps each sequence
// monotonic while making collisions between devices impossible.
constexpr unsigned TAG_BITS = 24;
constexpr uint64_t TAG_A = 0x0a0b0c;
constexpr uint64_t TAG_B = 0x0a0b0d;
auto ca = dht::crypto::generateIdentity("ca");
auto account = dht::crypto::generateIdentity("acc", ca, 4096, true);
auto deviceA = dht::crypto::generateIdentity("devA", account);
auto deviceB = dht::crypto::generateIdentity("devB", account);
uint64_t previous {0};
dht::crypto::RevocationList crlA;
for (unsigned i = 1; i <= 4; i++) {
crlA.revoke(*deviceA.second);
crlA.sign(account, {}, (uint64_t(i) << TAG_BITS) | TAG_A);
auto number = crlNumberValue(crlA.getNumber());
CPPUNIT_ASSERT_EQUAL(TAG_A, number & ((uint64_t(1) << TAG_BITS) - 1));
CPPUNIT_ASSERT(number > previous);
CPPUNIT_ASSERT_EQUAL(uint64_t(0), number >> 63);
previous = number;
}
// Both devices branch from the same counter but never mint the same number.
dht::crypto::RevocationList branchA;
branchA.revoke(*deviceA.second);
branchA.sign(account, {}, (uint64_t(9) << TAG_BITS) | TAG_A);
dht::crypto::RevocationList branchB;
branchB.revoke(*deviceB.second);
branchB.sign(account, {}, (uint64_t(9) << TAG_BITS) | TAG_B);
CPPUNIT_ASSERT(crlNumberValue(branchA.getNumber()) != crlNumberValue(branchB.getNumber()));
// Both are valid and revoke only their own target.
CPPUNIT_ASSERT(branchA.isSignedBy(*account.second));
CPPUNIT_ASSERT(branchB.isSignedBy(*account.second));
CPPUNIT_ASSERT(branchA.isRevoked(*deviceA.second));
CPPUNIT_ASSERT(not branchA.isRevoked(*deviceB.second));
CPPUNIT_ASSERT(branchB.isRevoked(*deviceB.second));
CPPUNIT_ASSERT(not branchB.isRevoked(*deviceA.second));
// A number minted by an older client is fully random, so bit 63 may be set. Masking
// it out and taking the counter part still yields a strictly greater number.
constexpr uint64_t legacy = 0xdead'beef'0123'4567ull;
dht::crypto::RevocationList legacyCrl;
legacyCrl.revoke(*deviceA.second);
legacyCrl.sign(account, {}, legacy);
auto counter = ((crlNumberValue(legacyCrl.getNumber()) & ~(uint64_t(1) << 63)) >> TAG_BITS) + 1;
legacyCrl.sign(account, {}, (counter << TAG_BITS) | TAG_A);
CPPUNIT_ASSERT(crlNumberValue(legacyCrl.getNumber()) > (legacy & ~(uint64_t(1) << 63)));
CPPUNIT_ASSERT_EQUAL(uint64_t(0), crlNumberValue(legacyCrl.getNumber()) >> 63);
}
void
CryptoTester::testCertificateRequest()
{ // Generate CA
{
// Generate CA
auto ca = dht::crypto::generateIdentity("Test CA");
// Generate signed request
@@ -316,15 +149,6 @@ CryptoTester::testCertificateSerialNumber()
};
auto serial = dht::crypto::Certificate(cert_pem).getSerialNumber();
CPPUNIT_ASSERT(std::equal(SERIAL.begin(), SERIAL.end(), serial.begin(), serial.end()));
auto key = dht::crypto::PrivateKey::generateEC();
for (unsigned i = 0; i < 32; i++) {
auto generated = dht::crypto::Certificate::generate(key, "generated", {});
auto generatedSerial = generated.getSerialNumber();
CPPUNIT_ASSERT(not generatedSerial.empty());
CPPUNIT_ASSERT_EQUAL(uint8_t {0}, uint8_t(generatedSerial.front() & 0x80));
CPPUNIT_ASSERT(std::any_of(generatedSerial.begin(), generatedSerial.end(), [](uint8_t byte) { return byte != 0; }));
}
}
void
-20
View File
@@ -12,11 +12,7 @@ class CryptoTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(CryptoTester);
CPPUNIT_TEST(testSignatureEncryption);
CPPUNIT_TEST(testPublicKeyPemIsPlainText);
CPPUNIT_TEST(testCertificateRevocation);
CPPUNIT_TEST(testRevocationListNumber);
CPPUNIT_TEST(testRevocationListExplicitNumber);
CPPUNIT_TEST(testRevocationListPartitionedNumber);
CPPUNIT_TEST(testCertificateRequest);
CPPUNIT_TEST(testCertificateSerialNumber);
CPPUNIT_TEST(testOcsp);
@@ -40,26 +36,10 @@ public:
* Test data signature, encryption and decryption
*/
void testSignatureEncryption();
/**
* Test that an exported public key is text only, with no embedded null byte
*/
void testPublicKeyPemIsPlainText();
/**
* Test certificate generation, validation and revocation
*/
void testCertificateRevocation();
/**
* Test the default CRL Number behavior: random on first signature, incremented after
*/
void testRevocationListNumber();
/**
* Test signing a revocation list with a caller-provided CRL Number
*/
void testRevocationListExplicitNumber();
/**
* Test that issuers sharing an authority can partition the CRL Number space
*/
void testRevocationListPartitionedNumber();
/**
* Test certificate requests
*/
+15 -15
View File
@@ -57,11 +57,11 @@ DhtProxyTester::tearDown()
std::condition_variable cv;
std::mutex cv_m;
nodeProxy->shutdown([&] {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
serverProxy.reset();
nodeProxy.reset();
@@ -81,11 +81,11 @@ DhtProxyTester::testGetPut()
auto val_data = val.data;
{
nodePeer.put(key, std::move(val), [&](bool) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
}
@@ -101,7 +101,7 @@ DhtProxyTester::testListen()
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
bool done = false;
@@ -111,7 +111,7 @@ DhtProxyTester::testListen()
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
@@ -121,7 +121,7 @@ DhtProxyTester::testListen()
std::vector<dht::Blob> values;
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
if (not expired) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
for (const auto& value : v)
values.emplace_back(value->data);
done = true;
@@ -156,7 +156,7 @@ DhtProxyTester::testResubscribeGetValues()
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
// If a peer sent a value, the listen operation from the client
@@ -164,7 +164,7 @@ DhtProxyTester::testResubscribeGetValues()
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
done = true;
cv.notify_all();
@@ -186,7 +186,7 @@ DhtProxyTester::testResubscribeGetValues()
std::vector<std::shared_ptr<dht::Value>> values;
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
if (not expired) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
values.insert(values.end(), v.begin(), v.end());
done = true;
cv.notify_all();
@@ -218,14 +218,14 @@ DhtProxyTester::testPutGet40KChars()
mtu.emplace_back((i % 2) ? 'T' : 'M');
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
bool done_put = false;
bool done_get = false;
// Act
dht::Value val {mtu};
nodePeer.put(key, std::move(val), [&](bool ok) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done_put = ok;
cv.notify_all();
});
@@ -238,7 +238,7 @@ DhtProxyTester::testPutGet40KChars()
return true;
},
[&](bool ok) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done_get = ok;
cv.notify_all();
});
@@ -316,12 +316,12 @@ DhtProxyTester::testShutdownStop()
std::mutex cv_m;
nodeTest->shutdown(
[&] {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
},
true);
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
}
CPPUNIT_ASSERT_EQUAL(2 * C, callback_count.load());
+7 -7
View File
@@ -51,11 +51,11 @@ DhtProxyStressTester::tearDown()
std::condition_variable cv;
std::mutex cv_m;
nodeProxy->shutdown([&] {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
serverProxy.reset();
nodeProxy.reset();
@@ -70,7 +70,7 @@ DhtProxyStressTester::testRepeatValues()
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
bool done = false;
@@ -80,7 +80,7 @@ DhtProxyStressTester::testRepeatValues()
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
@@ -90,7 +90,7 @@ DhtProxyStressTester::testRepeatValues()
std::vector<dht::Blob> values;
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
if (not expired) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
for (const auto& value : v)
values.emplace_back(value->data);
done = true;
@@ -132,7 +132,7 @@ DhtProxyStressTester::testRepeatValues()
val->id = id++;
nodePeer.put(key, val, [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
@@ -145,7 +145,7 @@ DhtProxyStressTester::testRepeatValues()
if (not expired) {
for (const auto& value : v) {
if (value->id == val->id) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
break;
-124
View File
@@ -9,8 +9,6 @@
#include <future>
#include <mutex>
#include <condition_variable>
#include <fstream>
#include <cstdio>
using namespace std::chrono_literals;
using namespace std::literals;
@@ -210,42 +208,6 @@ DhtRunnerTester::testImportValuesPreservesRemoteQuota()
restored.join();
}
void
DhtRunnerTester::testExportValuesUsesSystemTime()
{
auto key = dht::InfoHash::get("export-system-time");
const auto created = dht::clock::now();
msgpack::sbuffer importBuffer;
msgpack::packer<msgpack::sbuffer> pk(&importBuffer);
pk.pack_array(1);
pk.pack_array(2);
pk.pack(created.time_since_epoch().count());
dht::Value {std::string("persisted")}.msgpack_pack(pk);
node1.importValues({
dht::ValuesExport {key, {importBuffer.data(), importBuffer.data() + importBuffer.size()}}
});
auto exported = node1.exportValues();
CPPUNIT_ASSERT_EQUAL(size_t(1), exported.size());
msgpack::unpacked msg;
msgpack::unpack(msg, reinterpret_cast<const char*>(exported.front().second.data()), exported.front().second.size());
const auto values = msg.get();
CPPUNIT_ASSERT(values.type == msgpack::type::ARRAY);
CPPUNIT_ASSERT_EQUAL(uint32_t(1), values.via.array.size);
const auto& persistedValue = values.via.array.ptr[0];
CPPUNIT_ASSERT_EQUAL(uint32_t(4), persistedValue.via.array.size);
CPPUNIT_ASSERT(persistedValue.via.array.ptr[0].type == msgpack::type::EXT);
CPPUNIT_ASSERT(persistedValue.via.array.ptr[3].type == msgpack::type::EXT);
const auto persistedCreated = persistedValue.via.array.ptr[0].as<std::chrono::system_clock::time_point>();
const auto persistedExpiration = persistedValue.via.array.ptr[3].as<std::chrono::system_clock::time_point>();
const auto delta = std::chrono::system_clock::now() - persistedCreated;
CPPUNIT_ASSERT(delta >= std::chrono::system_clock::duration::zero());
CPPUNIT_ASSERT(delta < 5s);
CPPUNIT_ASSERT(persistedExpiration > persistedCreated);
}
void
DhtRunnerTester::testImportValuesPreservesStoredExpiration()
{
@@ -300,64 +262,6 @@ DhtRunnerTester::testImportValuesPreservesStoredExpiration()
restored.join();
}
void
DhtRunnerTester::testImportPermanentValueExpires()
{
static const dht::ValueType SHORT_TYPE {201, "Short perm", 500ms};
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
dht::DhtRunner restored;
restored.run(0, config);
restored.registerInsecureType(SHORT_TYPE);
// Ensure the runner is waiting on a previously computed scheduler deadline.
std::promise<void> loopCompleted;
auto loopCompletedFuture = loopCompleted.get_future();
restored.getNodeInfo([&loopCompleted](auto) { loopCompleted.set_value(); });
getFutureValue(std::move(loopCompletedFuture));
auto key = dht::InfoHash::get("import-permanent-expiration");
auto now = dht::clock::now();
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_array(1);
pk.pack_array(4);
pk.pack(now.time_since_epoch().count());
dht::Value {SHORT_TYPE.id, std::string("was-permanent")}.msgpack_pack(pk);
pk.pack_bin(0);
pk.pack(dht::clock::time_point::max().time_since_epoch().count());
restored.importValues({
dht::ValuesExport {key, {buffer.data(), buffer.data() + buffer.size()}}
});
auto containsValue = [&] {
for (const auto& [exportedKey, data] : restored.exportValues()) {
if (exportedKey != key)
continue;
msgpack::unpacked msg;
msgpack::unpack(msg, reinterpret_cast<const char*>(data.data()), data.size());
auto valarr = msg.get();
CPPUNIT_ASSERT(valarr.type == msgpack::type::ARRAY);
return valarr.via.array.size != 0;
}
return false;
};
CPPUNIT_ASSERT(containsValue());
auto timeout = dht::clock::now() + 2s;
while (containsValue() and dht::clock::now() < timeout)
std::this_thread::sleep_for(10ms);
CPPUNIT_ASSERT(!containsValue());
restored.shutdown();
restored.join();
}
void
DhtRunnerTester::testBootstrapSetsConnectingState()
{
@@ -559,34 +463,6 @@ DhtRunnerTester::testShutdownCompletesWithPendingPut()
clientNode.join();
}
void
DhtRunnerTester::testSavedPortFallback()
{
// A saved port that is already taken by another process (e.g. fast
// application restart while the old instance still holds it) must not
// prevent the node from starting: it should fall back to a random port.
dht::DhtRunner::Config config;
config.dht_config.node_config.persist_path = "/tmp/opendht_test_port_fallback";
std::string portFile = config.dht_config.node_config.persist_path + "_port.txt";
// node1 (from setUp) holds a port; save it as our persisted port.
auto takenPort = node1.getBoundPort();
{
std::ofstream out(portFile);
out << takenPort << std::endl << takenPort << std::endl;
}
dht::DhtRunner testNode;
CPPUNIT_ASSERT_NO_THROW(testNode.run(0, config));
CPPUNIT_ASSERT(testNode.getBoundPort());
CPPUNIT_ASSERT(testNode.getBoundPort() != takenPort);
testNode.shutdown();
testNode.join();
std::remove(portFile.c_str());
std::remove((config.dht_config.node_config.persist_path + "_port.txt").c_str());
}
void
DhtRunnerTester::testListen()
{
-6
View File
@@ -21,14 +21,11 @@ class DhtRunnerTester : public CppUnit::TestFixture
CPPUNIT_TEST(testListenLotOfBytes);
CPPUNIT_TEST(testIdOps);
CPPUNIT_TEST(testImportValuesPreservesRemoteQuota);
CPPUNIT_TEST(testExportValuesUsesSystemTime);
CPPUNIT_TEST(testImportValuesPreservesStoredExpiration);
CPPUNIT_TEST(testImportPermanentValueExpires);
CPPUNIT_TEST(testBootstrapSetsConnectingState);
CPPUNIT_TEST(testBootstrapThenPutNoRace);
CPPUNIT_TEST(testBootstrapMissingNodeThenPutFails);
CPPUNIT_TEST(testShutdownCompletesWithPendingPut);
CPPUNIT_TEST(testSavedPortFallback);
CPPUNIT_TEST_SUITE_END();
dht::DhtRunner node1 {};
@@ -68,14 +65,11 @@ public:
*/
void testIdOps();
void testImportValuesPreservesRemoteQuota();
void testExportValuesUsesSystemTime();
void testImportValuesPreservesStoredExpiration();
void testImportPermanentValueExpires();
void testBootstrapSetsConnectingState();
void testBootstrapThenPutNoRace();
void testBootstrapMissingNodeThenPutFails();
void testShutdownCompletesWithPendingPut();
void testSavedPortFallback();
/**
* Test listen method with lot of datas
*/
+5 -36
View File
@@ -8,7 +8,6 @@
#include <iostream>
#include <string>
#include <atomic>
#include <chrono>
#include <condition_variable>
@@ -333,43 +332,13 @@ HttpTester::test_parse_url_just_slash()
CPPUNIT_ASSERT_EQUAL(std::string("/"), parsed.target);
}
void
HttpTester::test_completed_resolver_callback_uses_io_thread()
{
asio::io_context ioContext;
const asio::ip::tcp::endpoint endpoint(asio::ip::address_v4::loopback(), 80);
dht::http::Resolver resolver(ioContext, {endpoint});
const auto callerThread = std::this_thread::get_id();
std::thread::id callbackThread;
asio::error_code callbackError;
std::vector<asio::ip::tcp::endpoint> resolvedEndpoints;
std::atomic_bool callbackCalled {false};
resolver.add_callback([&](const asio::error_code& ec, const auto& endpoints) {
callbackError = ec;
callbackThread = std::this_thread::get_id();
resolvedEndpoints = endpoints;
callbackCalled = true;
});
CPPUNIT_ASSERT(!callbackCalled);
std::thread ioThread([&] { ioContext.run(); });
ioThread.join();
CPPUNIT_ASSERT(callbackCalled);
CPPUNIT_ASSERT(!callbackError);
CPPUNIT_ASSERT(callbackThread != callerThread);
CPPUNIT_ASSERT_EQUAL(size_t(1), resolvedEndpoints.size());
CPPUNIT_ASSERT_EQUAL(endpoint, resolvedEndpoints.front());
}
void
HttpTester::test_send_json()
{
// Arrange
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
bool done = false;
unsigned int status = 0;
@@ -381,7 +350,7 @@ HttpTester::test_send_json()
"http://127.0.0.1:8080/key/key",
json,
[&](Json::Value value, const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
resp_val = std::move(value);
status = response.status_code;
done = true;
@@ -399,7 +368,7 @@ HttpTester::test_send_json()
request = std::make_shared<dht::http::Request>(serverProxy->io_context(),
"http://google.ca",
[&](const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
status = response.status_code;
done = true;
cv.notify_all();
@@ -413,7 +382,7 @@ HttpTester::test_send_json()
request = std::make_shared<dht::http::Request>(serverProxy->io_context(),
"https://google.ca",
[&](const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
status = response.status_code;
done = true;
cv.notify_all();
@@ -427,7 +396,7 @@ HttpTester::test_send_json()
request = std::make_shared<dht::http::Request>(serverProxy->io_context(),
"https://google.ca/sdbjklwGBIP",
[&](const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
status = response.status_code;
done = true;
cv.notify_all();
-2
View File
@@ -39,7 +39,6 @@ class HttpTester : public CppUnit::TestFixture
CPPUNIT_TEST(test_parse_url_complex_path_query_fragment);
CPPUNIT_TEST(test_parse_url_empty);
CPPUNIT_TEST(test_parse_url_just_slash);
CPPUNIT_TEST(test_completed_resolver_callback_uses_io_thread);
// send
CPPUNIT_TEST(test_send_json);
CPPUNIT_TEST_SUITE_END();
@@ -83,7 +82,6 @@ public:
void test_parse_url_complex_path_query_fragment();
void test_parse_url_empty();
void test_parse_url_just_slash();
void test_completed_resolver_callback_uses_io_thread();
/**
* Test send(json)
*/
-280
View File
@@ -1,280 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_mdns.h"
#include "../src/peer_discovery/dns_sd.h"
#include "../src/peer_discovery/mdns.h"
#include "../src/peer_discovery/mdns_transport.h"
#include <array>
#include <string>
namespace test {
using namespace dht::mdns;
CPPUNIT_TEST_SUITE_REGISTRATION(MdnsTester);
void
MdnsTester::testName()
{
const auto name = Name::fromString("Node._opendht._udp.local.");
CPPUNIT_ASSERT_EQUAL(std::string("Node._opendht._udp.local."), name.toString());
CPPUNIT_ASSERT(name == Name::fromString("node._OpenDHT._UDP.LOCAL"));
CPPUNIT_ASSERT_THROW(Name::fromString("bad..local."), ParseError);
CPPUNIT_ASSERT_THROW(Name::fromString(std::string(64, 'a') + ".local."), ParseError);
}
void
MdnsTester::testRecordRoundTrip()
{
const auto service = Name::fromString("_opendht._udp.local.");
const auto instance = Name::fromString("0123456789abcdef0123456789abcdef01234567._opendht._udp.local.");
const auto host = Name::fromString("0123456789abcdef0123456789abcdef01234567.local.");
Message message;
message.response = true;
message.authoritative = true;
message.answers.push_back({service, Type::PTR, CLASS_IN, false, 4500, PtrData {instance}});
message.additionals.push_back({
instance, Type::SRV, CLASS_IN, true, 120, SrvData {0, 0, 4222, host}
});
message.additionals.push_back(
{instance,
Type::TXT,
CLASS_IN,
true,
4500,
TxtData {{{'t', 'x', 't', 'v', 'e', 'r', 's', '=', '1'}, {'n', 'e', 't', '=', '0'}}}});
message.additionals.push_back({host, Type::A, CLASS_IN, true, 120, AData {{192, 0, 2, 1}}});
message.additionals.push_back(
{host, Type::AAAA, CLASS_IN, true, 120, AaaaData {{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}});
message.additionals.push_back({
host, Type::NSEC, CLASS_IN, true, 120, NsecData {host, {Type::A, Type::AAAA}}
});
const auto packet = encode(message);
const auto decoded = decode(packet.data(), packet.size());
CPPUNIT_ASSERT(decoded.response);
CPPUNIT_ASSERT(decoded.authoritative);
CPPUNIT_ASSERT_EQUAL(size_t(1), decoded.answers.size());
CPPUNIT_ASSERT_EQUAL(size_t(5), decoded.additionals.size());
CPPUNIT_ASSERT(decoded == message);
}
void
MdnsTester::testCompression()
{
Message message;
message.questions.push_back({Name::fromString("_opendht._udp.local."), Type::PTR, CLASS_IN, false});
message.questions.push_back({Name::fromString("node._opendht._udp.local."), Type::SRV, CLASS_IN, false});
const auto packet = encode(message);
CPPUNIT_ASSERT(std::find(packet.begin(), packet.end(), uint8_t {0xc0}) != packet.end());
CPPUNIT_ASSERT(decode(packet.data(), packet.size()) == message);
}
void
MdnsTester::testMalformedPackets()
{
const std::array<uint8_t, 3> truncated {
{0, 0, 0}
};
CPPUNIT_ASSERT_THROW(decode(truncated.data(), truncated.size()), ParseError);
const std::array<uint8_t, 18> compressionLoop {
{
0, 0,
0, 0,
0, 1,
0, 0,
0, 0,
0, 0,
0xc0, 0x0c,
0, static_cast<uint8_t>(Type::PTR),
0, CLASS_IN,
}
};
CPPUNIT_ASSERT_THROW(decode(compressionLoop.data(), compressionLoop.size()), ParseError);
}
void
MdnsTester::testPacketSizeLimit()
{
std::array<uint8_t, MAX_PACKET_SIZE + 1> oversized {};
CPPUNIT_ASSERT_THROW(decode(oversized.data(), oversized.size()), ParseError);
}
void
MdnsTester::testDnsSdAdvertisement()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
service.addresses.emplace_back(AaaaData {
{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
});
const auto message = dns_sd::announcement(service);
CPPUNIT_ASSERT(message.response);
CPPUNIT_ASSERT(message.authoritative);
CPPUNIT_ASSERT_EQUAL(size_t(1), message.answers.size());
CPPUNIT_ASSERT_EQUAL(size_t(4), message.additionals.size());
CPPUNIT_ASSERT_EQUAL(std::string("_opendht._udp.local."), message.answers.front().name.toString());
const auto resolved = dns_sd::resolve(message);
CPPUNIT_ASSERT(resolved);
CPPUNIT_ASSERT_EQUAL(service.nodeId, resolved->nodeId);
CPPUNIT_ASSERT_EQUAL(service.network, resolved->network);
CPPUNIT_ASSERT_EQUAL(service.port, resolved->port);
CPPUNIT_ASSERT_EQUAL(service.addresses.size(), resolved->addresses.size());
}
void
MdnsTester::testDnsSdGoodbye()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
const auto message = dns_sd::goodbye(service);
CPPUNIT_ASSERT(std::all_of(message.answers.begin(), message.answers.end(), [](const auto& record) {
return record.ttl == 0;
}));
CPPUNIT_ASSERT(std::all_of(message.additionals.begin(), message.additionals.end(), [](const auto& record) {
return record.ttl == 0;
}));
}
void
MdnsTester::testDnsSdValidation()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
auto message = dns_sd::announcement(service);
auto& txt = std::get<TxtData>(message.additionals[1].data);
txt.strings.push_back({'n', 'e', 't', '=', '4', '3'});
CPPUNIT_ASSERT(not dns_sd::resolve(message));
message = dns_sd::announcement(service);
auto& srv = std::get<SrvData>(message.additionals[0].data);
srv.target = Name::fromString("other.local.");
CPPUNIT_ASSERT(not dns_sd::resolve(message));
CPPUNIT_ASSERT_THROW(dns_sd::announcement({}), ParseError);
}
void
MdnsTester::testDnsSdQueryResponse()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
const auto query = dns_sd::browseQuery();
CPPUNIT_ASSERT(not query.response);
CPPUNIT_ASSERT_EQUAL(size_t(1), query.questions.size());
CPPUNIT_ASSERT(query.questions.front().name == dns_sd::serviceName());
CPPUNIT_ASSERT(query.questions.front().type == Type::PTR);
const auto response = dns_sd::respond(query, service);
CPPUNIT_ASSERT(response);
CPPUNIT_ASSERT(dns_sd::resolve(*response));
auto knownAnswerQuery = query;
knownAnswerQuery.answers = response->answers;
CPPUNIT_ASSERT(not dns_sd::respond(knownAnswerQuery, service));
knownAnswerQuery.answers.front().ttl = dns_sd::SERVICE_RECORD_TTL / 2;
CPPUNIT_ASSERT(dns_sd::respond(knownAnswerQuery, service));
Message metaQuery;
const auto metaName = Name::fromString("_services._dns-sd._udp.local.");
metaQuery.questions.push_back({metaName, Type::PTR, CLASS_IN, false});
const auto metaResponse = dns_sd::respond(metaQuery, service);
CPPUNIT_ASSERT(metaResponse);
CPPUNIT_ASSERT_EQUAL(size_t(1), metaResponse->answers.size());
CPPUNIT_ASSERT(metaResponse->answers.front().name == metaName);
CPPUNIT_ASSERT(std::get<PtrData>(metaResponse->answers.front().data).name == dns_sd::serviceName());
CPPUNIT_ASSERT(metaResponse->additionals.empty());
metaQuery.answers = metaResponse->answers;
CPPUNIT_ASSERT(not dns_sd::respond(metaQuery, service));
}
void
MdnsTester::testDnsSdCache()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
auto now = dns_sd::Cache::Clock::now();
dns_sd::Cache cache([&] { return now; });
auto ptr = dns_sd::announcement(service);
auto details = ptr;
ptr.additionals.clear();
details.answers.clear();
CPPUNIT_ASSERT(cache.update(ptr).empty());
const auto discovered = cache.update(details);
CPPUNIT_ASSERT_EQUAL(size_t(1), discovered.size());
CPPUNIT_ASSERT_EQUAL(service.nodeId, discovered.front().nodeId);
CPPUNIT_ASSERT_EQUAL(size_t(1), cache.services().size());
now += std::chrono::seconds(dns_sd::HOST_RECORD_TTL + 1);
CPPUNIT_ASSERT(cache.services().empty());
}
void
MdnsTester::testInterfaceScoping()
{
const std::vector<InterfaceAddress> addresses {
{1, 1, asio::ip::make_address("192.0.2.1"), true, false},
{1, 1, asio::ip::make_address("fe80::1"), true, false},
{2, 2, asio::ip::make_address("fe80::2"), true, false},
{3, 3, asio::ip::make_address("fe80::3"), false, false},
{4, 4, asio::ip::make_address("::1"), true, true },
};
const auto interfaces = groupInterfaces(addresses);
CPPUNIT_ASSERT_EQUAL(size_t(2), interfaces.size());
CPPUNIT_ASSERT_EQUAL(InterfaceId {1}, interfaces[0].id);
CPPUNIT_ASSERT_EQUAL(size_t(2), interfaces[0].addresses.size());
CPPUNIT_ASSERT_EQUAL(InterfaceId {2}, interfaces[1].id);
CPPUNIT_ASSERT_EQUAL(size_t(1), interfaces[1].addresses.size());
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.port = 4222;
service.addresses = interfaces[0].addresses;
const auto announcement = dns_sd::announcement(service);
const auto resolved = dns_sd::resolve(announcement);
CPPUNIT_ASSERT(resolved);
CPPUNIT_ASSERT(resolved->addresses == interfaces[0].addresses);
}
} // namespace test
-40
View File
@@ -1,40 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class MdnsTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(MdnsTester);
CPPUNIT_TEST(testName);
CPPUNIT_TEST(testRecordRoundTrip);
CPPUNIT_TEST(testCompression);
CPPUNIT_TEST(testMalformedPackets);
CPPUNIT_TEST(testPacketSizeLimit);
CPPUNIT_TEST(testDnsSdAdvertisement);
CPPUNIT_TEST(testDnsSdGoodbye);
CPPUNIT_TEST(testDnsSdValidation);
CPPUNIT_TEST(testDnsSdQueryResponse);
CPPUNIT_TEST(testDnsSdCache);
CPPUNIT_TEST(testInterfaceScoping);
CPPUNIT_TEST_SUITE_END();
public:
void testName();
void testRecordRoundTrip();
void testCompression();
void testMalformedPackets();
void testPacketSizeLimit();
void testDnsSdAdvertisement();
void testDnsSdGoodbye();
void testDnsSdValidation();
void testDnsSdQueryResponse();
void testDnsSdCache();
void testInterfaceScoping();
};
} // namespace test
+31 -154
View File
@@ -214,12 +214,9 @@ makeEngine(std::unique_ptr<TestDatagramSocket>&& socket,
std::mt19937_64& rd,
int& onNewNodeCalls)
{
net::NetworkConfig config {};
config.max_req_per_sec = -1;
config.max_peer_req_per_sec = -1;
return net::NetworkEngine(
myid,
config,
{},
std::move(socket),
{},
rd,
@@ -237,21 +234,6 @@ makeEngine(std::unique_ptr<TestDatagramSocket>&& socket,
[](Sp<Node>, const InfoHash&, const Blob&, const Value::Id&) { return net::RequestAnswer {}; });
}
static Sp<Node>
addPendingGetRequest(net::NetworkEngine& engine, const InfoHash& id, const SockAddr& addr, Tid tid)
{
auto node = engine.insertNode(id, addr);
auto request = std::make_shared<net::Request>(
MessageType::GetValues,
tid,
node,
Blob {},
[](const net::Request&, ParsedMessage&&) {},
[](const net::Request&, bool) {});
node->requested(request);
return node;
}
void
NetworkEngineTester::setUp()
{}
@@ -261,130 +243,24 @@ NetworkEngineTester::tearDown()
{}
void
NetworkEngineTester::testCompletesPartialSessionWithDataBeforeHeader()
NetworkEngineTester::testIgnoresUnknownPartialData()
{
Scheduler scheduler;
std::mt19937_64 rd(0);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto serialized = serializeValue("fragment before header");
Blob fragment = {1, 2, 3};
auto packet = makeValueDataPacketBlob(99, 0, 0, fragment);
auto from = makeIPv4("127.0.0.2", 5000);
addPendingGetRequest(engine, remoteId, from, 99);
auto initialOnNewNodeCalls = onNewNodeCalls;
auto fragment = makeValueDataPacketBlob(99, 0, 0, serialized);
auto header = makeReplyHeaderPacketBlob(remoteId, 99, {serialized.size()});
engine.processMessage(fragment.data(), fragment.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(initialOnNewNodeCalls, onNewNodeCalls);
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
}
void
NetworkEngineTester::testBuffersLargeValueBeforeHeader()
{
Scheduler scheduler;
std::mt19937_64 rd(8);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto serialized = serializeValue(std::string(57 * 1024, 'l'));
auto from = makeIPv4("127.0.0.2", 5008);
addPendingGetRequest(engine, remoteId, from, 101);
auto initialOnNewNodeCalls = onNewNodeCalls;
for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) {
auto end = std::min(offset + TEST_MTU, serialized.size());
Blob data(serialized.begin() + offset, serialized.begin() + end);
auto fragment = makeValueDataPacketBlob(101, 0, offset, data);
engine.processMessage(fragment.data(), fragment.size(), from);
}
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
auto header = makeReplyHeaderPacketBlob(remoteId, 101, {serialized.size()});
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
}
void
NetworkEngineTester::testIgnoresEmptyPartialData()
{
Scheduler scheduler;
std::mt19937_64 rd(7);
InfoHash myid = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto packet = makeValueDataPacketBlob(100, 0, 0, {});
auto from = makeIPv4("127.0.0.2", 5007);
engine.processMessage(packet.data(), packet.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(0, onNewNodeCalls);
}
void
NetworkEngineTester::testRejectsUnsolicitedPartialReply()
{
Scheduler scheduler;
std::mt19937_64 rd(9);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto from = makeIPv4("127.0.0.2", 5009);
auto header = makeReplyHeaderPacketBlob(remoteId, 102, {TEST_MTU});
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getNodeCacheSize());
}
void
NetworkEngineTester::testDropsMalformedCompletedPartialMessage()
{
Scheduler scheduler;
std::mt19937_64 rd(10);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto from = makeIPv4("127.0.0.2", 5010);
addPendingGetRequest(engine, remoteId, from, 103);
Blob malformed {0xc1};
auto header = makeReplyHeaderPacketBlob(remoteId, 103, {malformed.size()});
auto fragment = makeValueDataPacketBlob(103, 0, 0, malformed);
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
engine.processMessage(fragment.data(), fragment.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
auto earlyFrom = makeIPv4("127.0.0.2", 5011);
addPendingGetRequest(engine, remoteId, earlyFrom, 104);
auto earlyHeader = makeReplyHeaderPacketBlob(remoteId, 104, {malformed.size()});
auto earlyFragment = makeValueDataPacketBlob(104, 0, 0, malformed);
engine.processMessage(earlyFragment.data(), earlyFragment.size(), earlyFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
engine.processMessage(earlyHeader.data(), earlyHeader.size(), earlyFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
}
void
NetworkEngineTester::testCompletesPartialSessionFromSameSource()
{
@@ -398,8 +274,6 @@ NetworkEngineTester::testCompletesPartialSessionFromSameSource()
std::string data(TEST_MTU * 2 + 64, 'z');
auto serialized = serializeValue(data);
auto from = makeIPv4("127.0.0.2", 5001);
addPendingGetRequest(engine, remoteId, from, 7);
auto initialOnNewNodeCalls = onNewNodeCalls;
auto header = makeReplyHeaderPacketBlob(remoteId, 7, {serialized.size()});
engine.processMessage(header.data(), header.size(), from);
@@ -413,40 +287,45 @@ NetworkEngineTester::testCompletesPartialSessionFromSameSource()
}
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
CPPUNIT_ASSERT(onNewNodeCalls > 0);
}
void
NetworkEngineTester::testSeparatesPartialSessionsBySource()
NetworkEngineTester::testKeepsSessionForWrongSourceFragment()
{
Scheduler scheduler;
std::mt19937_64 rd(2);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash firstRemoteId = InfoHash::getRandom(rd);
InfoHash secondRemoteId = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto serialized = serializeValue("same transaction, different peers");
auto firstFrom = makeIPv4("127.0.0.2", 5002);
auto secondFrom = makeIPv4("127.0.0.3", 5002);
addPendingGetRequest(engine, firstRemoteId, firstFrom, 11);
addPendingGetRequest(engine, secondRemoteId, secondFrom, 11);
auto initialOnNewNodeCalls = onNewNodeCalls;
auto firstHeader = makeReplyHeaderPacketBlob(firstRemoteId, 11, {serialized.size()});
auto secondHeader = makeReplyHeaderPacketBlob(secondRemoteId, 11, {serialized.size()});
std::string data(TEST_MTU + 32, 'w');
auto serialized = serializeValue(data);
auto goodFrom = makeIPv4("127.0.0.2", 5002);
auto badFrom = makeIPv4("127.0.0.3", 5002);
auto header = makeReplyHeaderPacketBlob(remoteId, 11, {serialized.size()});
engine.processMessage(firstHeader.data(), firstHeader.size(), firstFrom);
engine.processMessage(secondHeader.data(), secondHeader.size(), secondFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 2, engine.getPartialCount());
auto fragment = makeValueDataPacketBlob(11, 0, 0, serialized);
engine.processMessage(fragment.data(), fragment.size(), firstFrom);
engine.processMessage(header.data(), header.size(), goodFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
engine.processMessage(fragment.data(), fragment.size(), secondFrom);
auto firstEnd = std::min(TEST_MTU, serialized.size());
Blob firstFragment(serialized.begin(), serialized.begin() + firstEnd);
auto firstPacket = makeValueDataPacketBlob(11, 0, 0, firstFragment);
engine.processMessage(firstPacket.data(), firstPacket.size(), badFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(0, onNewNodeCalls);
engine.processMessage(firstPacket.data(), firstPacket.size(), goodFrom);
if (firstEnd < serialized.size()) {
Blob tail(serialized.begin() + firstEnd, serialized.end());
auto tailPacket = makeValueDataPacketBlob(11, 0, firstEnd, tail);
engine.processMessage(tailPacket.data(), tailPacket.size(), goodFrom);
}
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
CPPUNIT_ASSERT(onNewNodeCalls > 0);
}
void
@@ -549,10 +428,8 @@ NetworkEngineTester::testListenConfirmationUpdatesSearchNodeToken()
localDht.onListenDone(node, answer, sr);
CPPUNIT_ASSERT(searchNodePtr->token == answer.ntoken);
/* last_get_reply should NOT be updated by listen confirmation,
so that periodic find_node requests still refresh the search table. */
CPPUNIT_ASSERT(searchNodePtr->last_get_reply == time_point::min());
CPPUNIT_ASSERT(!searchNodePtr->isSynced(localDht.scheduler.time()));
CPPUNIT_ASSERT(searchNodePtr->last_get_reply == localDht.scheduler.time());
CPPUNIT_ASSERT(searchNodePtr->isSynced(localDht.scheduler.time()));
}
void
+4 -12
View File
@@ -13,13 +13,9 @@ class NetworkEngineTester : public CppUnit::TestFixture
#ifdef _MSC_VER
CPPUNIT_TEST(testDisabledOnMsvc);
#else
CPPUNIT_TEST(testCompletesPartialSessionWithDataBeforeHeader);
CPPUNIT_TEST(testBuffersLargeValueBeforeHeader);
CPPUNIT_TEST(testIgnoresEmptyPartialData);
CPPUNIT_TEST(testRejectsUnsolicitedPartialReply);
CPPUNIT_TEST(testDropsMalformedCompletedPartialMessage);
CPPUNIT_TEST(testIgnoresUnknownPartialData);
CPPUNIT_TEST(testCompletesPartialSessionFromSameSource);
CPPUNIT_TEST(testSeparatesPartialSessionsBySource);
CPPUNIT_TEST(testKeepsSessionForWrongSourceFragment);
CPPUNIT_TEST(testListenConfirmationCarriesToken);
CPPUNIT_TEST(testListenConfirmationUpdatesSearchNodeToken);
CPPUNIT_TEST(testListenReopensSocketAfterNodeExpiration);
@@ -34,13 +30,9 @@ public:
#ifdef _MSC_VER
void testDisabledOnMsvc();
#else
void testCompletesPartialSessionWithDataBeforeHeader();
void testBuffersLargeValueBeforeHeader();
void testIgnoresEmptyPartialData();
void testRejectsUnsolicitedPartialReply();
void testDropsMalformedCompletedPartialMessage();
void testIgnoresUnknownPartialData();
void testCompletesPartialSessionFromSameSource();
void testSeparatesPartialSessionsBySource();
void testKeepsSessionForWrongSourceFragment();
void testListenConfirmationCarriesToken();
void testListenConfirmationUpdatesSearchNodeToken();
void testListenReopensSocketAfterNodeExpiration();
-98
View File
@@ -328,104 +328,6 @@ ParsedMessageTester::testParseRejectsInvalidValuesField()
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsEmptyFieldSelection()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_R);
pk.pack_map(1);
pk.pack(KEY_REQ_FIELDS);
pk.pack_map(2);
pk.pack("f");
pk.pack_array(0);
pk.pack("v");
pk.pack_array(0);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack(KEY_R);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsEmptyErrorArray()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_E);
pk.pack_array(0);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack(KEY_E);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsNonMapRequest()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
pk.pack(KEY_A);
pk.pack_array(2);
pk.pack(1u);
pk.pack(2u);
pk.pack(KEY_Q);
pk.pack(QUERY_PING);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack("q");
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseErrorOnlyMessageKeepsRequestFieldsEmpty()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
// An error message with no request map: the error array must not be walked
// as if it were a sequence of key/value pairs.
pk.pack_map(3);
pk.pack(KEY_E);
pk.pack_array(2);
pk.pack(401u);
pk.pack("unauthorized");
pk.pack(KEY_TID);
pk.pack(9u);
pk.pack(KEY_Y);
pk.pack(KEY_E);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
parsed.msgpack_unpack(msg.get());
CPPUNIT_ASSERT(parsed.type == MessageType::Error);
CPPUNIT_ASSERT_EQUAL((uint16_t) 401, parsed.error_code);
CPPUNIT_ASSERT(not parsed.id);
CPPUNIT_ASSERT(parsed.token.empty());
CPPUNIT_ASSERT(parsed.values.empty());
CPPUNIT_ASSERT(parsed.nodes4_raw.empty());
CPPUNIT_ASSERT(parsed.nodes6_raw.empty());
}
void
ParsedMessageTester::testParseIgnoresIncompleteValueDataEntry()
{
-8
View File
@@ -24,10 +24,6 @@ class ParsedMessageTester : public CppUnit::TestFixture
CPPUNIT_TEST(testParseRejectsNonMapPacket);
CPPUNIT_TEST(testParseRejectsInvalidValueDataPayload);
CPPUNIT_TEST(testParseRejectsInvalidValuesField);
CPPUNIT_TEST(testParseRejectsEmptyFieldSelection);
CPPUNIT_TEST(testParseRejectsEmptyErrorArray);
CPPUNIT_TEST(testParseRejectsNonMapRequest);
CPPUNIT_TEST(testParseErrorOnlyMessageKeepsRequestFieldsEmpty);
CPPUNIT_TEST(testParseIgnoresIncompleteValueDataEntry);
CPPUNIT_TEST(testCompleteEmpty);
CPPUNIT_TEST(testCompleteIncomplete);
@@ -57,10 +53,6 @@ public:
void testParseRejectsNonMapPacket();
void testParseRejectsInvalidValueDataPayload();
void testParseRejectsInvalidValuesField();
void testParseRejectsEmptyFieldSelection();
void testParseRejectsEmptyErrorArray();
void testParseRejectsNonMapRequest();
void testParseErrorOnlyMessageKeepsRequestFieldsEmpty();
void testParseIgnoresIncompleteValueDataEntry();
void testCompleteEmpty();
void testCompleteIncomplete();
+60 -23
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: MIT
#include "test_peerdiscovery.h"
#include "opendht/value.h"
#include <mutex>
#include <condition_variable>
@@ -10,8 +11,24 @@ namespace test {
using namespace std::literals;
constexpr dht::NetId NETWORK_ID = 10;
constexpr in_port_t NODE_PORT = 50000;
constexpr unsigned MULTICAST_PORT = 2222;
constexpr auto DHT_NODE_NAME {"dht"sv};
constexpr auto JAMI_NODE_NAME {"jami"sv};
struct DhtNode
{
dht::InfoHash nodeid;
in_port_t node_port;
dht::NetId nid;
MSGPACK_DEFINE(nodeid, node_port, nid)
};
struct JamiNode
{
int num;
char cha;
std::string str;
MSGPACK_DEFINE(num, cha, str)
};
CPPUNIT_TEST_SUITE_REGISTRATION(PeerDiscoveryTester);
@@ -22,40 +39,60 @@ PeerDiscoveryTester::setUp()
void
PeerDiscoveryTester::testMulticastToTwoNodes()
{
const auto nodeId = dht::InfoHash::get("opendht01");
DhtNode dhtNode;
dhtNode.nid = 10;
dhtNode.node_port = 50000;
dhtNode.nodeid = dht::InfoHash::get("opendht01");
JamiNode jamiNode;
jamiNode.num = 100;
jamiNode.cha = 'a';
jamiNode.str = "jami01";
std::mutex lock;
std::condition_variable cv;
unsigned discovered {0};
unsigned wrongNetwork {0};
unsigned countDht {0};
unsigned countJami {0};
{
std::unique_lock l(lock);
dht::PeerDiscovery publisher;
dht::PeerDiscovery browser;
dht::PeerDiscovery otherNetworkBrowser;
std::unique_lock<std::mutex> l(lock);
dht::PeerDiscovery testDht(MULTICAST_PORT);
dht::PeerDiscovery testJami(MULTICAST_PORT);
browser.startDiscovery(NETWORK_ID, [&](const dht::InfoHash& peerId, dht::SockAddr&& address) {
CPPUNIT_ASSERT_EQUAL(nodeId, peerId);
CPPUNIT_ASSERT_EQUAL(NODE_PORT, address.getPort());
testJami.startDiscovery<DhtNode>(DHT_NODE_NAME, [&](DhtNode&& v, dht::SockAddr&&) {
CPPUNIT_ASSERT_EQUAL(dhtNode.node_port, v.node_port);
CPPUNIT_ASSERT_EQUAL(dhtNode.nodeid, v.nodeid);
CPPUNIT_ASSERT_EQUAL(dhtNode.nid, v.nid);
{
std::lock_guard l(lock);
discovered++;
std::lock_guard<std::mutex> l(lock);
countDht++;
}
cv.notify_all();
});
otherNetworkBrowser.startDiscovery(NETWORK_ID + 1, [&](const dht::InfoHash&, dht::SockAddr&&) {
std::lock_guard l(lock);
wrongNetwork++;
testJami.startDiscovery(JAMI_NODE_NAME, [&](msgpack::object&& obj, dht::SockAddr&&) {
auto v = obj.as<JamiNode>();
CPPUNIT_ASSERT_EQUAL(jamiNode.num, v.num);
CPPUNIT_ASSERT_EQUAL(jamiNode.cha, v.cha);
CPPUNIT_ASSERT_EQUAL(jamiNode.str, v.str);
{
std::lock_guard<std::mutex> l(lock);
countJami++;
}
cv.notify_all();
});
publisher.startPublish(nodeId, NETWORK_ID, NODE_PORT);
CPPUNIT_ASSERT(cv.wait_for(l, std::chrono::seconds(5), [&] { return discovered > 0; }));
CPPUNIT_ASSERT_EQUAL(0u, wrongNetwork);
testDht.startPublish(DHT_NODE_NAME, dhtNode);
CPPUNIT_ASSERT(cv.wait_for(l, std::chrono::seconds(5), [&] { return countDht > 0; }));
testDht.startPublish(JAMI_NODE_NAME, jamiNode);
CPPUNIT_ASSERT(cv.wait_for(l, std::chrono::seconds(5), [&] { return countDht > 1 and countJami > 0; }));
// we don't verify count values since its a continious multicasting
l.unlock();
publisher.stopPublish();
browser.stopDiscovery();
otherNetworkBrowser.stopDiscovery();
testDht.stopPublish(DHT_NODE_NAME);
testDht.stopPublish(JAMI_NODE_NAME);
testJami.stopDiscovery(DHT_NODE_NAME);
testJami.stopDiscovery(JAMI_NODE_NAME);
}
}
-40
View File
@@ -80,46 +80,6 @@ ThreadPoolTester::testContext()
CPPUNIT_ASSERT_EQUAL(N, count.load());
}
void
ThreadPoolTester::testJoinNonEmptyTaskQueue()
{
dht::ThreadPool pool(2);
constexpr unsigned N = 128;
std::atomic_uint count {0};
// Schedule enough work so that the task queue is non-empty when calling
// pool.join() below.
for (unsigned i = 0; i < N; i++)
pool.run([&] {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
count++;
});
std::atomic_bool joined {false};
std::condition_variable cv;
std::thread joinThread([&] {
CPPUNIT_ASSERT(count.load() < N);
pool.join();
joined = true;
cv.notify_one();
});
// After calling join(), all pending tasks should complete, and then all
// threads in the pool should join.
std::mutex mtx;
std::unique_lock lock(mtx);
bool poolJoined = cv.wait_for(lock, std::chrono::seconds(10), [&] { return joined.load(); });
if (!poolJoined) {
// Detach joinThread if we timed out, otherwise the test can crash.
joinThread.detach();
} else {
joinThread.join();
}
CPPUNIT_ASSERT_EQUAL_MESSAGE("Some tasks were not completed", N, count.load());
CPPUNIT_ASSERT_MESSAGE("Thread pool failed to join before timeout", poolJoined);
}
void
ThreadPoolTester::tearDown()
{}
-2
View File
@@ -14,7 +14,6 @@ class ThreadPoolTester : public CppUnit::TestFixture
CPPUNIT_TEST(testThreadPool);
CPPUNIT_TEST(testExecutor);
CPPUNIT_TEST(testContext);
CPPUNIT_TEST(testJoinNonEmptyTaskQueue);
CPPUNIT_TEST_SUITE_END();
public:
@@ -30,7 +29,6 @@ public:
void testThreadPool();
void testExecutor();
void testContext();
void testJoinNonEmptyTaskQueue();
};
} // namespace test
-103
View File
@@ -8,8 +8,6 @@
// opendht
#include "opendht/value.h"
#include "opendht/crypto.h"
#include <msgpack.hpp>
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(ValueTester);
@@ -61,105 +59,4 @@ ValueTester::testFilter()
void
ValueTester::tearDown()
{}
void
ValueTester::testPushTypeMsgpackRoundTrip()
{
dht::Value original {(const uint8_t*) "hello", 5};
original.id = 42;
original.priority = 3;
original.pushType = "audioCall";
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
original.msgpack_pack(pk);
msgpack::unpacked msg;
msgpack::unpack(msg, buffer.data(), buffer.size());
dht::Value restored(msg.get());
CPPUNIT_ASSERT_EQUAL(original.id, restored.id);
CPPUNIT_ASSERT_EQUAL(original.priority, restored.priority);
CPPUNIT_ASSERT_EQUAL(original.pushType, restored.pushType);
}
void
ValueTester::testPushTypeAbsentAfterUnpack()
{
dht::Value withPt {(const uint8_t*) "data", 4};
withPt.id = 1;
withPt.pushType = "videoCall";
withPt.priority = 2;
msgpack::sbuffer buf1;
msgpack::packer<msgpack::sbuffer> pk1(&buf1);
withPt.msgpack_pack(pk1);
msgpack::unpacked msg1;
msgpack::unpack(msg1, buf1.data(), buf1.size());
dht::Value reused;
reused.msgpack_unpack(msg1.get());
CPPUNIT_ASSERT_EQUAL(std::string("videoCall"), reused.pushType);
CPPUNIT_ASSERT_EQUAL(2u, reused.priority);
dht::Value plain {(const uint8_t*) "data", 4};
plain.id = 2;
msgpack::sbuffer buf2;
msgpack::packer<msgpack::sbuffer> pk2(&buf2);
plain.msgpack_pack(pk2);
msgpack::unpacked msg2;
msgpack::unpack(msg2, buf2.data(), buf2.size());
reused.msgpack_unpack(msg2.get());
CPPUNIT_ASSERT_EQUAL(std::string(), reused.pushType);
CPPUNIT_ASSERT_EQUAL(0u, reused.priority);
}
void
ValueTester::testPushTypePreservedAfterEncrypt()
{
auto key = dht::crypto::PrivateKey::generate();
const auto& pubkey = key.getPublicKey();
dht::Value original {(const uint8_t*) "secret", 6};
original.id = 99;
original.priority = 1;
original.pushType = "audioCall";
auto encrypted = original.encrypt(key, pubkey);
CPPUNIT_ASSERT_EQUAL(original.pushType, encrypted.pushType);
CPPUNIT_ASSERT_EQUAL(original.priority, encrypted.priority);
CPPUNIT_ASSERT_EQUAL(original.id, encrypted.id);
CPPUNIT_ASSERT(encrypted.isEncrypted());
}
void
ValueTester::testPushTypeJsonRoundTrip()
{
#ifdef OPENDHT_JSONCPP
dht::Value original {(const uint8_t*) "hello", 5};
original.id = 42;
original.priority = 1;
original.pushType = "videoCall";
dht::Value restored(original.toJson());
CPPUNIT_ASSERT_EQUAL(original.id, restored.id);
CPPUNIT_ASSERT_EQUAL(original.priority, restored.priority);
CPPUNIT_ASSERT_EQUAL(original.pushType, restored.pushType);
// No pushType: the field must stay absent from the JSON and empty after parse.
dht::Value plain {(const uint8_t*) "data", 4};
plain.id = 7;
auto json = plain.toJson();
CPPUNIT_ASSERT(!json.isMember("pt"));
dht::Value plainRestored(json);
CPPUNIT_ASSERT_EQUAL(std::string(), plainRestored.pushType);
#endif
}
} // namespace test
-8
View File
@@ -13,10 +13,6 @@ class ValueTester : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(ValueTester);
CPPUNIT_TEST(testConstructors);
CPPUNIT_TEST(testFilter);
CPPUNIT_TEST(testPushTypeMsgpackRoundTrip);
CPPUNIT_TEST(testPushTypeAbsentAfterUnpack);
CPPUNIT_TEST(testPushTypePreservedAfterEncrypt);
CPPUNIT_TEST(testPushTypeJsonRoundTrip);
CPPUNIT_TEST_SUITE_END();
public:
@@ -36,10 +32,6 @@ public:
* Test compare operators
*/
void testFilter();
void testPushTypeMsgpackRoundTrip();
void testPushTypeAbsentAfterUnpack();
void testPushTypePreservedAfterEncrypt();
void testPushTypeJsonRoundTrip();
};
} // namespace test
+1 -1
View File
@@ -123,7 +123,7 @@ main(int argc, char** argv)
iss >> idstr;
std::getline(iss, line);
dht.putEncrypted(room,
PkId(idstr),
InfoHash(idstr),
dht::ImMessage(IdDist()(tools::rd), std::move(line), now),
[](bool ok) {
// dht.cancelPut(room, id);
+2 -2
View File
@@ -481,7 +481,7 @@ cmd_loop(std::shared_ptr<DhtRunner>& node,
auto value = std::make_shared<dht::Value>(dht::ValueType::USER_DATA.id,
std::vector<uint8_t> {v.begin(), v.end()});
value->user_type = "text/plain";
node->putEncrypted(id, PkId(tostr), std::move(value), [start](bool ok) {
node->putEncrypted(id, InfoHash(tostr), std::move(value), [start](bool ok) {
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Put encrypted: " << (ok ? "success" : "failure") << " (took "
<< print_duration(end - start) << ')' << std::endl;
@@ -649,7 +649,7 @@ main(int argc, char** argv)
});
// wait for shutdown
std::unique_lock lk(m);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]() { return done; });
node->join();
+1 -1
View File
@@ -126,7 +126,7 @@ main(int argc, char** argv)
{
std::mutex m;
std::unique_lock lk(m);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]() { return done.load() == 0; });
}
+2 -2
View File
@@ -68,7 +68,7 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
unsigned max = PINGPONG_MAX * n_parallel;
auto ping = [&](DhtRunner& node, InfoHash h) {
std::lock_guard lk(m);
std::lock_guard<std::mutex> lk(m);
if (i < max) {
i++;
node.put(h, Value("hey"));
@@ -93,7 +93,7 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
ping(pong_node, locs[i].first);
{
std::unique_lock lk(m);
std::unique_lock<std::mutex> lk(m);
if (not cv.wait_for(lk, std::chrono::minutes(1), [&]() { return i == max; })) {
throw std::runtime_error(std::string("Timeout: ") + std::to_string(i));
}
+1 -1
View File
@@ -421,7 +421,7 @@ struct ServiceRunner
{
bool wait()
{
std::unique_lock lock(m);
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&] { return terminate.load(); });
return !terminate;
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"default-registry": {
"kind": "git",
"baseline": "f3e10653cc27d62a37a3763cd84b38bca07c6075",
"baseline": "66c0373dc7fca549e5803087b9487edfe3aca0a1",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "opendht",
"version-string": "4.3.0",
"version-string": "3.7.6",
"port-version": 1,
"description": "OpenDHT: A lightweight C++17 Distributed Hash Table library.",
"homepage": "https://opendht.net",