mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
Merge branch 'master' into readme-update-gcc-version
This commit is contained in:
59 files changed
+5765
-1884
No files matched your search
@@ -48,6 +48,11 @@ Makefile.in
|
||||
|
||||
# Python
|
||||
*.pyc
|
||||
python/opendht.cpp
|
||||
python/setup.py
|
||||
|
||||
# Doxygen
|
||||
doc/Doxyfile
|
||||
|
||||
# git backup files
|
||||
*.orig
|
||||
|
||||
+75
-7
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required (VERSION 3.1)
|
||||
project (opendht)
|
||||
set (opendht_VERSION_MAJOR 1)
|
||||
set (opendht_VERSION_MINOR 3.5)
|
||||
set (opendht_VERSION_MINOR 6.0)
|
||||
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
|
||||
set (PACKAGE_VERSION ${opendht_VERSION})
|
||||
set (VERSION "${opendht_VERSION}")
|
||||
@@ -15,9 +15,18 @@ option (OPENDHT_TOOLS "Build DHT tools" ON)
|
||||
option (OPENDHT_SYSTEMD "Install systemd module" OFF)
|
||||
option (OPENDHT_ARGON2 "Use included argon2 sources" OFF)
|
||||
option (OPENDHT_LTO "Build with LTO" OFF)
|
||||
option (OPENDHT_SANITIZE "Build with address sanitizer and stack protector" OFF)
|
||||
option (OPENDHT_PROXY_SERVER "Enable DHT proxy server, use Restbed and jsoncpp" OFF)
|
||||
option (OPENDHT_PUSH_NOTIFICATIONS "Enable push notifications support" OFF)
|
||||
option (OPENDHT_PROXY_SERVER_IDENTITY "Allow clients to use the node identity" OFF)
|
||||
option (OPENDHT_PROXY_CLIENT "Enable DHT proxy client, use Restbed and jsoncpp" OFF)
|
||||
|
||||
find_package(Doxygen)
|
||||
option (OPENDHT_DOCUMENTATION "Create and install the HTML based API documentation (requires Doxygen)" ${DOXYGEN_FOUND})
|
||||
|
||||
# Dependencies
|
||||
list (APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
find_package (Threads)
|
||||
find_package (PkgConfig)
|
||||
find_package (GnuTLS 3.3 REQUIRED)
|
||||
pkg_search_module (Nettle nettle)
|
||||
@@ -36,11 +45,18 @@ if (NOT OPENDHT_ARGON2)
|
||||
set(OPENDHT_ARGON2 ON)
|
||||
endif()
|
||||
endif ()
|
||||
if (OPENDHT_PROXY_SERVER OR OPENDHT_PROXY_CLIENT)
|
||||
find_package(Restbed REQUIRED)
|
||||
pkg_search_module(Jsoncpp jsoncpp)
|
||||
endif()
|
||||
|
||||
# Build flags
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
set (CMAKE_CXX_STANDARD_REQUIRED on)
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -Wno-return-type -Wall -Wextra -Wnon-virtual-dtor -pedantic-errors -fvisibility=hidden")
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-return-type -Wall -Wextra -Wnon-virtual-dtor -pedantic-errors -fvisibility=hidden")
|
||||
if (OPENDHT_SANITIZE)
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fstack-protector-strong")
|
||||
endif ()
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT")
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
@@ -69,7 +85,14 @@ endif ()
|
||||
if (Nettle_INCLUDE_DIRS)
|
||||
include_directories (SYSTEM "${Nettle_INCLUDE_DIRS}")
|
||||
endif ()
|
||||
if (RESTBED_INCLUDE)
|
||||
include_directories (SYSTEM "${RESTBED_INCLUDE}")
|
||||
endif ()
|
||||
if (Jsoncpp_INCLUDE_DIRS)
|
||||
include_directories (SYSTEM "${Jsoncpp_INCLUDE_DIRS}")
|
||||
endif ()
|
||||
link_directories (${Nettle_LIBRARY_DIRS})
|
||||
link_directories (${Jsoncpp_LIBRARY_DIRS})
|
||||
include_directories (
|
||||
./
|
||||
include/
|
||||
@@ -85,6 +108,7 @@ set (libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
|
||||
set (includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
set (bindir "${CMAKE_INSTALL_FULL_BINDIR}")
|
||||
set (sysconfdir "${CMAKE_INSTALL_FULL_SYSCONFDIR}")
|
||||
set (top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
# Sources
|
||||
list (APPEND opendht_SOURCES
|
||||
@@ -98,6 +122,8 @@ list (APPEND opendht_SOURCES
|
||||
src/storage.h
|
||||
src/listener.h
|
||||
src/search.h
|
||||
src/net.h
|
||||
src/parsed_message.h
|
||||
src/request.h
|
||||
src/callbacks.cpp
|
||||
src/routing_table.cpp
|
||||
@@ -120,11 +146,11 @@ list (APPEND opendht_HEADERS
|
||||
include/opendht/node.h
|
||||
include/opendht/value.h
|
||||
include/opendht/dht.h
|
||||
include/opendht/dht_interface.h
|
||||
include/opendht/callbacks.h
|
||||
include/opendht/routing_table.h
|
||||
include/opendht/node_cache.h
|
||||
include/opendht/network_engine.h
|
||||
include/opendht/net.h
|
||||
include/opendht/scheduler.h
|
||||
include/opendht/rate_limiter.h
|
||||
include/opendht/securedht.h
|
||||
@@ -134,6 +160,48 @@ list (APPEND opendht_HEADERS
|
||||
include/opendht/indexation/pht.h
|
||||
)
|
||||
|
||||
if (OPENDHT_PROXY_SERVER)
|
||||
add_definitions(-DOPENDHT_PROXY_SERVER=true)
|
||||
if (OPENDHT_PROXY_SERVER_IDENTITY)
|
||||
add_definitions(-DOPENDHT_PROXY_SERVER_IDENTITY=true)
|
||||
else ()
|
||||
add_definitions(-DOPENDHT_PROXY_SERVER_IDENTITY=false)
|
||||
endif()
|
||||
list (APPEND opendht_HEADERS
|
||||
include/opendht/dht_proxy_server.h
|
||||
)
|
||||
list (APPEND opendht_SOURCES
|
||||
src/dht_proxy_server.cpp
|
||||
)
|
||||
if (OPENDHT_PUSH_NOTIFICATIONS)
|
||||
add_definitions(-DOPENDHT_PUSH_NOTIFICATIONS=true)
|
||||
else ()
|
||||
add_definitions(-DOPENDHT_PUSH_NOTIFICATIONS=false)
|
||||
endif ()
|
||||
else ()
|
||||
add_definitions(-DENABLE_PROXY_SERVER=false)
|
||||
endif ()
|
||||
|
||||
if (OPENDHT_PROXY_CLIENT)
|
||||
add_definitions(-DOPENDHT_PROXY_CLIENT=true)
|
||||
list (APPEND opendht_HEADERS
|
||||
include/opendht/dht_proxy_client.h
|
||||
)
|
||||
list (APPEND opendht_SOURCES
|
||||
src/dht_proxy_client.cpp
|
||||
)
|
||||
else ()
|
||||
add_definitions(-DOPENDHT_PROXY_CLIENT=false)
|
||||
endif ()
|
||||
|
||||
if (OPENDHT_PROXY_SERVER OR OPENDHT_PROXY_CLIENT)
|
||||
list (APPEND opendht_SOURCES
|
||||
src/base64.h
|
||||
src/base64.cpp
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
||||
if(OPENDHT_ARGON2)
|
||||
# make sure argon2 submodule is up to date and initialized
|
||||
message("Initializing Argon2 submodule")
|
||||
@@ -167,7 +235,7 @@ if (OPENDHT_STATIC)
|
||||
target_link_libraries(opendht-static ${argon2_LIBRARIES})
|
||||
target_include_directories(opendht-static SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
|
||||
endif ()
|
||||
target_link_libraries(opendht-static ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES})
|
||||
target_link_libraries(opendht-static ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Restbed_LIBRARIES} ${Jsoncpp_LIBRARIES})
|
||||
install (TARGETS opendht-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
|
||||
endif ()
|
||||
|
||||
@@ -180,15 +248,15 @@ if (OPENDHT_SHARED)
|
||||
set_target_properties (opendht PROPERTIES SOVERSION ${opendht_VERSION_MAJOR} VERSION ${opendht_VERSION})
|
||||
target_compile_definitions(opendht PRIVATE OPENDHT_BUILD)
|
||||
if (OPENDHT_LTO)
|
||||
target_link_libraries(opendht -flto -fuse-linker-plugin)
|
||||
target_link_libraries(opendht PRIVATE -flto -fuse-linker-plugin)
|
||||
endif ()
|
||||
if (OPENDHT_ARGON2)
|
||||
target_include_directories(opendht SYSTEM PRIVATE argon2)
|
||||
else ()
|
||||
target_link_libraries(opendht ${argon2_LIBRARIES})
|
||||
target_link_libraries(opendht PRIVATE ${argon2_LIBRARIES})
|
||||
target_include_directories(opendht SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
|
||||
endif ()
|
||||
target_link_libraries(opendht ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES})
|
||||
target_link_libraries(opendht PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Restbed_LIBRARIES} ${Jsoncpp_LIBRARIES})
|
||||
install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
|
||||
endif ()
|
||||
|
||||
|
||||
+14
-3
@@ -19,11 +19,14 @@
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="wingetopt.c" />
|
||||
<ClCompile Include="..\src\base64.cpp" />
|
||||
<ClCompile Include="..\src\callbacks.cpp" />
|
||||
<ClCompile Include="..\src\crypto.cpp" />
|
||||
<ClCompile Include="..\src\default_types.cpp" />
|
||||
<ClCompile Include="..\src\dht.cpp" />
|
||||
<ClCompile Include="..\src\dhtrunner.cpp" />
|
||||
<ClCompile Include="..\src\dht_proxy_server.cpp" />
|
||||
<ClCompile Include="..\src\indexation\pht.cpp" />
|
||||
<ClCompile Include="..\src\infohash.cpp" />
|
||||
<ClCompile Include="..\src\log.cpp" />
|
||||
@@ -36,12 +39,16 @@
|
||||
<ClCompile Include="..\src\value.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="unistd.h" />
|
||||
<ClInclude Include="wingetopt.h" />
|
||||
<ClInclude Include="..\include\opendht.h" />
|
||||
<ClInclude Include="..\include\opendht\callbacks.h" />
|
||||
<ClInclude Include="..\include\opendht\crypto.h" />
|
||||
<ClInclude Include="..\include\opendht\def.h" />
|
||||
<ClInclude Include="..\include\opendht\default_types.h" />
|
||||
<ClInclude Include="..\include\opendht\dht.h" />
|
||||
<ClInclude Include="..\include\opendht\dhtrunner.h" />
|
||||
<ClInclude Include="..\include\opendht\dht_proxy_server.h" />
|
||||
<ClInclude Include="..\include\opendht\indexation\pht.h" />
|
||||
<ClInclude Include="..\include\opendht\infohash.h" />
|
||||
<ClInclude Include="..\include\opendht\log.h" />
|
||||
@@ -50,14 +57,18 @@
|
||||
<ClInclude Include="..\include\opendht\node.h" />
|
||||
<ClInclude Include="..\include\opendht\node_cache.h" />
|
||||
<ClInclude Include="..\include\opendht\rate_limiter.h" />
|
||||
<ClInclude Include="..\include\opendht\request.h" />
|
||||
<ClInclude Include="..\include\opendht\rng.h" />
|
||||
<ClInclude Include="..\include\opendht\routing_table.h" />
|
||||
<ClInclude Include="..\include\opendht\scheduler.h" />
|
||||
<ClInclude Include="..\include\opendht\securedht.h" />
|
||||
<ClInclude Include="..\include\opendht\sockaddr.h" />
|
||||
<ClInclude Include="..\include\opendht\utils.h" />
|
||||
<ClInclude Include="..\include\opendht\value.h" />
|
||||
<ClInclude Include="..\src\base64.h" />
|
||||
<ClInclude Include="..\src\listener.h" />
|
||||
<ClInclude Include="..\src\net.h" />
|
||||
<ClInclude Include="..\src\parsed_message.h" />
|
||||
<ClInclude Include="..\src\request.h" />
|
||||
<ClInclude Include="..\src\search.h" />
|
||||
<ClInclude Include="..\src\storage.h" />
|
||||
</ItemGroup>
|
||||
@@ -211,9 +222,9 @@ copy ..\include\opendht.h $(OutDir)\include\</Command>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)contrib\build\include;$(ProjectDir)..\include;$(ProjectDir)..\include\opendht;$(ProjectDir)contrib\build\msgpack-c\include;$(ProjectDir)contrib\build\argon2\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)contrib\build\include;$(ProjectDir)..\include;$(ProjectDir)..\include\opendht;$(ProjectDir)contrib\build\msgpack-c\include;$(ProjectDir)contrib\build\argon2\include;$(ProjectDir)..\..\include;$(ProjectDir)..\..\argon2\include</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32_NATIVE;WIN32_LEAN_AND_MEAN;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<DisableSpecificWarnings>4804;4800;4101;4267;4244;4503;</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings>4804;4800;4101;4267;4244;4503;4273;</DisableSpecificWarnings>
|
||||
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions>
|
||||
<ProgramDataBaseFileName>$(OutDir)\lib\x64\$(TargetName).pdb</ProgramDataBaseFileName>
|
||||
</ClCompile>
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
<ClCompile Include="..\src\node_cache.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\indexation\pht.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\routing_table.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -46,80 +43,102 @@
|
||||
<ClCompile Include="..\src\value.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\base64.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\dht_proxy_server.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\indexation\pht.cpp">
|
||||
<Filter>Source Files\indexation</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="wingetopt.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\opendht\callbacks.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\crypto.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\default_types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\dht.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\dhtrunner.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\infohash.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<ClInclude Include="..\include\opendht\def.h" />
|
||||
<ClInclude Include="..\include\opendht\sockaddr.h" />
|
||||
<ClInclude Include="..\src\net.h" />
|
||||
<ClInclude Include="..\src\parsed_message.h" />
|
||||
<ClInclude Include="..\src\base64.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\listener.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\log.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<ClInclude Include="..\src\request.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\log_enable.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<ClInclude Include="..\src\search.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\network_engine.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\node.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\node_cache.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<ClInclude Include="..\src\storage.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\indexation\pht.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<ClInclude Include="..\include\opendht\callbacks.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\crypto.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\default_types.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\dht.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\dht_proxy_server.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\dhtrunner.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\infohash.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\log.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\log_enable.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\network_engine.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\node.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\node_cache.h">
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\rate_limiter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\request.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\rng.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\routing_table.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\scheduler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\search.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\securedht.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\storage.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\value.h">
|
||||
<Filter>Header Files</Filter>
|
||||
<Filter>Header Files\opendht</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\opendht\indexation\pht.h">
|
||||
<Filter>Header Files\opendht\indexation</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="unistd.h" />
|
||||
<ClInclude Include="wingetopt.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
@@ -128,5 +147,14 @@
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{d1ab5bfe-3ab1-45ee-9324-b4b071887668}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\indexation">
|
||||
<UniqueIdentifier>{939134d3-d5a2-4458-bca8-a10ea9042ea2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\opendht">
|
||||
<UniqueIdentifier>{0b46e2d0-4874-404c-bae3-fae8d49b85b8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\opendht\indexation">
|
||||
<UniqueIdentifier>{03bd5139-2ae5-41b0-a77b-944814e1b2b6}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,7 +1,7 @@
|
||||
<img src="https://raw.githubusercontent.com/savoirfairelinux/opendht/master/resources/opendht_logo_512.png" width="100" align="right">
|
||||
<br>
|
||||
<h1 style="margin-top:10px">
|
||||
<a id="user-content-opendht-" class="anchor" href="/savoirfairelinux/opendht/blob/master/README.md#opendht-" aria-hidden="true"></a>OpenDHT
|
||||
<a id="user-content-opendht-" class="anchor" href="/savoirfairelinux/opendht/blob/master/README.md#opendht-" aria-hidden="true"></a>OpenDHT
|
||||
</h1>
|
||||
|
||||
A lightweight C++11 Distributed Hash Table implementation originally based on https://github.com/jech/dht by Juliusz Chroboczek.
|
||||
@@ -18,7 +18,7 @@ See the wiki: <https://github.com/savoirfairelinux/opendht/wiki>
|
||||
|
||||
#### How-to build and install
|
||||
|
||||
Build instructions : <https://github.com/savoirfairelinux/opendht/wiki/Build-the-library>
|
||||
Build instructions: <https://github.com/savoirfairelinux/opendht/wiki/Build-the-library>
|
||||
|
||||
#### How-to build a simple client app
|
||||
```bash
|
||||
@@ -91,7 +91,9 @@ for r in results:
|
||||
- msgpack-c 1.2+, used for data serialization.
|
||||
- GnuTLS 3.3+, used for cryptographic operations.
|
||||
- Nettle 2.4+, a GnuTLS dependency for crypto.
|
||||
- Build tested with GCC 5.4+ (GNU/Linux, Android, Windows with MinGW), Clang/LLVM (Linux, macOS).
|
||||
- (optional) restbed used for the REST API. commit fb84213e170bc171fecd825a8e47ed9f881a12cd (https://github.com/AmarOk1412/restbed/tree/async_read_until)
|
||||
- (optional) jsoncpp 1.7.4-3+, used for the REST API.
|
||||
- Build tested with GCC 5.2+ (GNU/Linux, Android, Windows with MinGW), Clang/LLVM (Linux, macOS).
|
||||
- Build tested with Microsoft Visual Studio 2015
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
find_path (RESTBED_INCLUDE restbed
|
||||
HINTS
|
||||
"/usr/include"
|
||||
"/usr/local/include"
|
||||
"/opt/local/include"
|
||||
)
|
||||
|
||||
if (RESTBED_INCLUDE)
|
||||
message(STATUS "${green}Found Restbed: ${RESTBED_INCLUDE}")
|
||||
else()
|
||||
message(FATAL_ERROR "${red}Failed to locate Restbed.}")
|
||||
endif()
|
||||
|
||||
if (RESTBED_INCLUDE)
|
||||
set(RESTBED_FOUND TRUE)
|
||||
set(Restbed_LIBRARIES restbed)
|
||||
endif()
|
||||
+50
-2
@@ -1,7 +1,7 @@
|
||||
dnl define macros
|
||||
m4_define([opendht_major_version], 1)
|
||||
m4_define([opendht_minor_version], 3)
|
||||
m4_define([opendht_patch_version], 5)
|
||||
m4_define([opendht_minor_version], 6)
|
||||
m4_define([opendht_patch_version], 0)
|
||||
m4_define([opendht_version],
|
||||
[opendht_major_version.opendht_minor_version.opendht_patch_version])
|
||||
|
||||
@@ -128,6 +128,54 @@ AM_COND_IF([ENABLE_TOOLS], [
|
||||
])
|
||||
])
|
||||
|
||||
AC_ARG_ENABLE([proxy_server], AS_HELP_STRING([--enable-proxy-server], [Enable proxy server ability]), proxy_server=yes, proxy_server=no)
|
||||
AM_CONDITIONAL(ENABLE_PROXY_SERVER, test x$proxy_server == xyes)
|
||||
AC_ARG_ENABLE([push_notifications], AS_HELP_STRING([--enable-push-notifications], [Enable push notifications support]), push_notifications=yes, push_notifications=no)
|
||||
AM_CONDITIONAL(ENABLE_PUSH_NOTIFICATIONS, test x$push_notifications == xyes)
|
||||
AC_ARG_ENABLE([proxy_server_identity], AS_HELP_STRING([--enable-proxy-server-identity],
|
||||
[Enable proxy server ability]), proxy_server_identity=yes, proxy_server_identity=no)
|
||||
AM_CONDITIONAL(ENABLE_PROXY_SERVER_IDENTITY, test x$proxy_server_identity == xyes)
|
||||
AC_ARG_ENABLE([proxy_client], AS_HELP_STRING([--enable-proxy-client], [Enable proxy client ability]), proxy_client=yes, proxy_client=no)
|
||||
AM_CONDITIONAL(ENABLE_PROXY_CLIENT, test x$proxy_client == xyes)
|
||||
|
||||
AM_COND_IF([ENABLE_PROXY_SERVER], [
|
||||
AC_CHECK_LIB(restbed, exit,, AC_MSG_ERROR([Missing restbed files]))
|
||||
PKG_CHECK_MODULES([Jsoncpp], [jsoncpp >= 1.7.2])
|
||||
CPPFLAGS+=" -DOPENDHT_PROXY_SERVER=true -ljsoncpp -lrestbed"
|
||||
], [
|
||||
CPPFLAGS+=" -DOPENDHT_PROXY_SERVER=false"
|
||||
])
|
||||
|
||||
AM_COND_IF([ENABLE_PUSH_NOTIFICATIONS], [
|
||||
CPPFLAGS+=" -DOPENDHT_PUSH_NOTIFICATIONS=true"
|
||||
], [
|
||||
CPPFLAGS+=" -DOPENDHT_PUSH_NOTIFICATIONS=false"
|
||||
])
|
||||
|
||||
AC_ARG_ENABLE([proxy_server_identity], AS_HELP_STRING([--enable-proxy-server-identity],
|
||||
[Enable proxy server ability]), proxy_server_identity=yes, proxy_server_identity=no)
|
||||
AM_CONDITIONAL(ENABLE_PROXY_SERVER_IDENTITY, test x$proxy_server_identity == xyes -a x$proxy_server == xyes)
|
||||
AM_COND_IF([ENABLE_PROXY_SERVER_IDENTITY], [
|
||||
CPPFLAGS+=" -DOPENDHT_PROXY_SERVER_IDENTITY=true"
|
||||
], [
|
||||
CPPFLAGS+=" -DOPENDHT_PROXY_SERVER_IDENTITY=false"
|
||||
])
|
||||
|
||||
AM_COND_IF([ENABLE_PROXY_CLIENT], [
|
||||
CPPFLAGS+=" -DOPENDHT_PROXY_CLIENT=true"
|
||||
], [
|
||||
CPPFLAGS+=" -DOPENDHT_PROXY_CLIENT=false"
|
||||
])
|
||||
|
||||
AM_CONDITIONAL(PROXY_CLIENT_OR_SERVER, test x$proxy_client == xyes | test x$proxy_server == xyes)
|
||||
AM_COND_IF([PROXY_CLIENT_OR_SERVER], [
|
||||
AC_CHECK_LIB(restbed, exit,, AC_MSG_ERROR([Missing restbed files]))
|
||||
PKG_CHECK_MODULES([Jsoncpp], [jsoncpp >= 1.7.4])
|
||||
CPPFLAGS="${CPPFLAGS} ${Jsoncpp_CFLAGS}"
|
||||
LDFLAGS="${LDFLAGS} ${Jsoncpp_LIBS} -lrestbed"
|
||||
], [])
|
||||
|
||||
|
||||
AC_CONFIG_FILES([doc/Doxyfile doc/Makefile])
|
||||
|
||||
dnl Configure setup.py if we build the python module
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
if (OPENDHT_TOOLS)
|
||||
INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dhtnode.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)
|
||||
endif ()
|
||||
|
||||
if (OPENDHT_DOCUMENTATION)
|
||||
if (NOT DOXYGEN_FOUND)
|
||||
message(FATAL_ERROR "Doxygen is needed to build the documentation.")
|
||||
endif()
|
||||
configure_file (Doxyfile.in Doxyfile @ONLY)
|
||||
add_custom_target(doc ALL
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/opendht)
|
||||
endif()
|
||||
@@ -19,6 +19,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "opendht/dhtrunner.h"
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
#include "opendht/dht_proxy_server.h"
|
||||
#endif
|
||||
#include "opendht/log.h"
|
||||
#include "opendht/default_types.h"
|
||||
#include "opendht/indexation/pht.h"
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -26,6 +27,10 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
#include <json/json.h>
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
namespace dht {
|
||||
|
||||
struct Node;
|
||||
@@ -40,13 +45,21 @@ enum class NodeStatus {
|
||||
};
|
||||
|
||||
struct OPENDHT_PUBLIC NodeStats {
|
||||
unsigned good_nodes,
|
||||
dubious_nodes,
|
||||
cached_nodes,
|
||||
incoming_nodes;
|
||||
unsigned table_depth;
|
||||
unsigned good_nodes {0},
|
||||
dubious_nodes {0},
|
||||
cached_nodes {0},
|
||||
incoming_nodes {0};
|
||||
unsigned table_depth {0};
|
||||
unsigned getKnownNodes() const { return good_nodes + dubious_nodes; }
|
||||
std::string toString() const;
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
/**
|
||||
* Build a json object from a NodeStats
|
||||
*/
|
||||
Json::Value toJson() const;
|
||||
NodeStats() {};
|
||||
explicit NodeStats(const Json::Value& v);
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,10 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
namespace dht {
|
||||
|
||||
/**
|
||||
* Contains all crypto primitives
|
||||
*/
|
||||
namespace crypto {
|
||||
|
||||
class OPENDHT_PUBLIC CryptoException : public std::runtime_error {
|
||||
@@ -583,6 +587,9 @@ using SecureBlob = secure_vector<uint8_t>;
|
||||
OPENDHT_PUBLIC Identity generateIdentity(const std::string& name, Identity ca, unsigned key_length, bool is_ca);
|
||||
OPENDHT_PUBLIC Identity generateIdentity(const std::string& name = "dhtnode", Identity ca = {}, unsigned key_length = 4096);
|
||||
|
||||
OPENDHT_PUBLIC Identity generateEcIdentity(const std::string& name, Identity ca, bool is_ca);
|
||||
OPENDHT_PUBLIC Identity generateEcIdentity(const std::string& name = "dhtnode", Identity ca = {});
|
||||
|
||||
|
||||
/**
|
||||
* Performs SHA512, SHA256 or SHA1, depending on hash_length.
|
||||
@@ -594,6 +601,8 @@ OPENDHT_PUBLIC Identity generateIdentity(const std::string& name = "dhtnode", Id
|
||||
*/
|
||||
OPENDHT_PUBLIC Blob hash(const Blob& data, size_t hash_length = 512/8);
|
||||
|
||||
OPENDHT_PUBLIC void hash(const uint8_t* data, size_t data_length, uint8_t* hash, size_t hash_length);
|
||||
|
||||
/**
|
||||
* Generates an encryption key from a text password,
|
||||
* making the key longer to bruteforce.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "value.h"
|
||||
#include "sockaddr.h"
|
||||
|
||||
namespace dht {
|
||||
enum class ImStatus : uint8_t {
|
||||
@@ -45,7 +46,7 @@ public:
|
||||
|
||||
static Value::Filter getFilter() { return {}; }
|
||||
|
||||
static bool storePolicy(InfoHash key, std::shared_ptr<Value>& value, InfoHash from, const sockaddr* from_addr, socklen_t from_len);
|
||||
static bool storePolicy(InfoHash key, std::shared_ptr<Value>& value, const InfoHash& from, const SockAddr&);
|
||||
|
||||
static Value::Filter ServiceFilter(std::string s);
|
||||
|
||||
@@ -92,7 +93,7 @@ public:
|
||||
static Value::Filter getFilter() {
|
||||
return Value::Filter::chain(
|
||||
BaseClass::getFilter(),
|
||||
[](const Value& v){ return v.recipient != InfoHash(); }
|
||||
[](const Value& v) { return static_cast<bool>(v.recipient); }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,16 +205,12 @@ private:
|
||||
public:
|
||||
static const ValueType TYPE;
|
||||
|
||||
IpServiceAnnouncement(in_port_t p = 0) {
|
||||
ss.ss_family = 0;
|
||||
setPort(p);
|
||||
IpServiceAnnouncement(sa_family_t family = AF_UNSPEC, in_port_t p = 0) {
|
||||
addr.setFamily(family);
|
||||
addr.setPort(p);
|
||||
}
|
||||
|
||||
IpServiceAnnouncement(const sockaddr* sa, socklen_t sa_len) {
|
||||
ss.ss_family = 0;
|
||||
if (sa)
|
||||
std::copy_n((const uint8_t*)sa, sa_len, (uint8_t*)&ss);
|
||||
}
|
||||
IpServiceAnnouncement(const SockAddr& sa) : addr(sa) {}
|
||||
|
||||
IpServiceAnnouncement(const Blob& b) {
|
||||
msgpack_unpack(unpackMsg(b).get());
|
||||
@@ -222,53 +219,40 @@ public:
|
||||
template <typename Packer>
|
||||
void msgpack_pack(Packer& pk) const
|
||||
{
|
||||
pk.pack_array(2);
|
||||
pk.pack(reinterpret_cast<const sockaddr_in*>(&ss)->sin_port);
|
||||
if (ss.ss_family == AF_INET) {
|
||||
pk.pack_bin(sizeof(in_addr));
|
||||
pk.pack_bin_body((const char*)&reinterpret_cast<const sockaddr_in*>(&ss)->sin_addr, sizeof(in_addr));
|
||||
} else if (ss.ss_family == AF_INET6) {
|
||||
pk.pack_bin(sizeof(in6_addr));
|
||||
pk.pack_bin_body((const char*)&reinterpret_cast<const sockaddr_in6*>(&ss)->sin6_addr, sizeof(in6_addr));
|
||||
}
|
||||
pk.pack_bin(addr.getLength());
|
||||
pk.pack_bin_body((const char*)addr.get(), addr.getLength());
|
||||
}
|
||||
|
||||
virtual void msgpack_unpack(msgpack::object o)
|
||||
{
|
||||
if (o.type != msgpack::type::ARRAY) throw msgpack::type_error();
|
||||
if (o.via.array.size < 2) throw msgpack::type_error();
|
||||
reinterpret_cast<sockaddr_in*>(&ss)->sin_port = o.via.array.ptr[0].as<in_port_t>();
|
||||
auto ip_dat = o.via.array.ptr[1].as<Blob>();
|
||||
if (ip_dat.size() == sizeof(in_addr))
|
||||
std::copy(ip_dat.begin(), ip_dat.end(), (char*)&reinterpret_cast<sockaddr_in*>(&ss)->sin_addr);
|
||||
else if (ip_dat.size() == sizeof(in6_addr))
|
||||
std::copy(ip_dat.begin(), ip_dat.end(), (char*)&reinterpret_cast<sockaddr_in6*>(&ss)->sin6_addr);
|
||||
if (o.type == msgpack::type::BIN)
|
||||
addr = {(sockaddr*)o.via.bin.ptr, (socklen_t)o.via.bin.size};
|
||||
else
|
||||
throw msgpack::type_error();
|
||||
}
|
||||
|
||||
in_port_t getPort() const {
|
||||
return ntohs(reinterpret_cast<const sockaddr_in*>(&ss)->sin_port);
|
||||
return addr.getPort();
|
||||
}
|
||||
void setPort(in_port_t p) {
|
||||
reinterpret_cast<sockaddr_in*>(&ss)->sin_port = htons(p);
|
||||
addr.setPort(p);
|
||||
}
|
||||
|
||||
sockaddr_storage getPeerAddr() const {
|
||||
return ss;
|
||||
const SockAddr& getPeerAddr() const {
|
||||
return addr;
|
||||
}
|
||||
|
||||
virtual const ValueType& getType() const {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
static bool storePolicy(InfoHash, std::shared_ptr<Value>&, InfoHash, const sockaddr*, socklen_t);
|
||||
static bool storePolicy(InfoHash, std::shared_ptr<Value>&, const InfoHash&, const SockAddr&);
|
||||
|
||||
/** print value for debugging */
|
||||
friend std::ostream& operator<< (std::ostream&, const IpServiceAnnouncement&);
|
||||
|
||||
private:
|
||||
sockaddr_storage ss;
|
||||
SockAddr addr;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+24
-47
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -26,7 +27,7 @@
|
||||
#include "scheduler.h"
|
||||
#include "routing_table.h"
|
||||
#include "callbacks.h"
|
||||
#include "log_enable.h"
|
||||
#include "dht_interface.h"
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
@@ -58,22 +59,16 @@ struct LocalListener;
|
||||
* Must be given open UDP sockets and ::periodic must be
|
||||
* called regularly.
|
||||
*/
|
||||
class OPENDHT_PUBLIC Dht {
|
||||
class OPENDHT_PUBLIC Dht final : public DhtInterface {
|
||||
public:
|
||||
|
||||
// [[deprecated]]
|
||||
using NodeExport = dht::NodeExport;
|
||||
|
||||
// [[deprecated]]
|
||||
using Status = NodeStatus;
|
||||
|
||||
Dht();
|
||||
|
||||
/**
|
||||
* Initialise the Dht with two open sockets (for IPv4 and IP6)
|
||||
* and an ID for the node.
|
||||
*/
|
||||
Dht(int s, int s6, Config config);
|
||||
Dht(const int& s, const int& s6, Config config);
|
||||
virtual ~Dht();
|
||||
|
||||
/**
|
||||
@@ -103,18 +98,6 @@ public:
|
||||
*/
|
||||
bool isRunning(sa_family_t af = 0) const;
|
||||
|
||||
/**
|
||||
* Enable or disable logging of DHT internal messages
|
||||
*/
|
||||
void setLoggers(LogMethod error = NOLOG, LogMethod warn = NOLOG, LogMethod debug = NOLOG);
|
||||
|
||||
/**
|
||||
* Only print logs related to the given InfoHash (if given), or disable filter (if zeroes).
|
||||
*/
|
||||
void setLogFilter(const InfoHash& f) {
|
||||
DHT_LOG.setFilter(f);
|
||||
}
|
||||
|
||||
virtual void registerType(const ValueType& type) {
|
||||
types[type.id] = type;
|
||||
}
|
||||
@@ -168,8 +151,8 @@ public:
|
||||
* @param key the key for which to query data for.
|
||||
* @param cb a function called when new values are found on the network.
|
||||
* It should return false to stop the operation.
|
||||
* @param donecb a function called when the operation is complete.
|
||||
cb and donecb won't be called again afterward.
|
||||
* @param done_cb a function called when the operation is complete.
|
||||
cb and done_cb won't be called again afterward.
|
||||
* @param q a query used to filter values on the remotes before they send a
|
||||
* response.
|
||||
*/
|
||||
@@ -248,7 +231,7 @@ public:
|
||||
*
|
||||
* @return a token to cancel the listener later.
|
||||
*/
|
||||
virtual size_t listen(const InfoHash&, GetCallback, Value::Filter&&={}, Where&& w = {});
|
||||
virtual size_t listen(const InfoHash&, GetCallback, Value::Filter={}, Where w = {});
|
||||
virtual size_t listen(const InfoHash& key, GetCallbackSimple cb, Value::Filter f={}, Where w = {}) {
|
||||
return listen(key, bindGetCb(cb), std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
@@ -307,10 +290,8 @@ public:
|
||||
|
||||
std::vector<SockAddr> getPublicAddress(sa_family_t family = 0);
|
||||
|
||||
protected:
|
||||
Logger DHT_LOG;
|
||||
bool logFilerEnable_ {};
|
||||
InfoHash logFiler_ {};
|
||||
void pushNotificationReceived(const std::map<std::string, std::string>&) {}
|
||||
void resubscribe(unsigned) {}
|
||||
|
||||
private:
|
||||
|
||||
@@ -339,7 +320,7 @@ private:
|
||||
static constexpr unsigned MAX_HASHES {16384};
|
||||
|
||||
/* The maximum number of searches we keep data about. */
|
||||
static constexpr unsigned MAX_SEARCHES {2048};
|
||||
static constexpr unsigned MAX_SEARCHES {16384};
|
||||
|
||||
static constexpr std::chrono::minutes MAX_STORAGE_MAINTENANCE_EXPIRE_TIME {10};
|
||||
|
||||
@@ -402,7 +383,6 @@ private:
|
||||
Scheduler scheduler;
|
||||
Sp<Scheduler::Job> nextNodesConfirmation {};
|
||||
Sp<Scheduler::Job> nextStorageMaintenance {};
|
||||
time_point mybucket_grow_time {time_point::min()}, mybucket6_grow_time {time_point::min()};
|
||||
|
||||
net::NetworkEngine network_engine;
|
||||
unsigned pending_pings4 {0};
|
||||
@@ -422,14 +402,13 @@ private:
|
||||
|
||||
// Storage
|
||||
void storageAddListener(const InfoHash& id, const Sp<Node>& node, size_t tid, Query&& = {});
|
||||
bool storageStore(const InfoHash& id, const Sp<Value>& value, time_point created, const SockAddr* sa = nullptr);
|
||||
bool storageStore(const InfoHash& id, const Sp<Value>& value, time_point created, const SockAddr& sa = {});
|
||||
void expireStore();
|
||||
void expireStorage(InfoHash h);
|
||||
void expireStore(decltype(store)::iterator);
|
||||
|
||||
void storageChanged(const InfoHash& id, Storage& st, ValueStorage&);
|
||||
std::string printStorageLog(const decltype(store)::value_type&) const;
|
||||
void printStorageQuota(std::ostream& out, const decltype(store_quota)::value_type& ip) const;
|
||||
|
||||
/**
|
||||
* For a given storage, if values don't belong there anymore because this
|
||||
@@ -505,7 +484,7 @@ private:
|
||||
* @param query The query sent to the node.
|
||||
*/
|
||||
void searchNodeGetDone(const net::Request& status,
|
||||
net::NetworkEngine::RequestAnswer&& answer,
|
||||
net::RequestAnswer&& answer,
|
||||
std::weak_ptr<Search> ws,
|
||||
Sp<Query> query);
|
||||
|
||||
@@ -554,48 +533,46 @@ private:
|
||||
|
||||
bool neighbourhoodMaintenance(RoutingTable&);
|
||||
|
||||
void processMessage(const uint8_t *buf, size_t buflen, const SockAddr&);
|
||||
|
||||
void onError(Sp<net::Request> node, net::DhtProtocolException e);
|
||||
/* when our address is reported by a distant peer. */
|
||||
void onReportedAddr(const InfoHash& id, const SockAddr&);
|
||||
/* when we receive a ping request */
|
||||
net::NetworkEngine::RequestAnswer onPing(Sp<Node> node);
|
||||
net::RequestAnswer onPing(Sp<Node> node);
|
||||
/* when we receive a "find node" request */
|
||||
net::NetworkEngine::RequestAnswer onFindNode(Sp<Node> node, const InfoHash& hash, want_t want);
|
||||
net::RequestAnswer onFindNode(Sp<Node> node, const InfoHash& hash, want_t want);
|
||||
void onFindNodeDone(const Sp<Node>& status,
|
||||
net::NetworkEngine::RequestAnswer& a,
|
||||
net::RequestAnswer& a,
|
||||
Sp<Search> sr);
|
||||
/* when we receive a "get values" request */
|
||||
net::NetworkEngine::RequestAnswer onGetValues(Sp<Node> node,
|
||||
net::RequestAnswer onGetValues(Sp<Node> node,
|
||||
const InfoHash& hash,
|
||||
want_t want,
|
||||
const Query& q);
|
||||
void onGetValuesDone(const Sp<Node>& status,
|
||||
net::NetworkEngine::RequestAnswer& a,
|
||||
net::RequestAnswer& a,
|
||||
Sp<Search>& sr,
|
||||
const Sp<Query>& orig_query);
|
||||
/* when we receive a listen request */
|
||||
net::NetworkEngine::RequestAnswer onListen(Sp<Node> node,
|
||||
net::RequestAnswer onListen(Sp<Node> node,
|
||||
const InfoHash& hash,
|
||||
const Blob& token,
|
||||
size_t socket_id,
|
||||
const Query& query);
|
||||
void onListenDone(const Sp<Node>& status,
|
||||
net::NetworkEngine::RequestAnswer& a,
|
||||
net::RequestAnswer& a,
|
||||
Sp<Search>& sr);
|
||||
/* when we receive an announce request */
|
||||
net::NetworkEngine::RequestAnswer onAnnounce(Sp<Node> node,
|
||||
net::RequestAnswer onAnnounce(Sp<Node> node,
|
||||
const InfoHash& hash,
|
||||
const Blob& token,
|
||||
const std::vector<Sp<Value>>& v,
|
||||
const time_point& created);
|
||||
net::NetworkEngine::RequestAnswer onRefresh(Sp<Node> node,
|
||||
net::RequestAnswer onRefresh(Sp<Node> node,
|
||||
const InfoHash& hash,
|
||||
const Blob& token,
|
||||
const Value::Id& vid);
|
||||
void onAnnounceDone(const Sp<Node>& status,
|
||||
net::NetworkEngine::RequestAnswer& a,
|
||||
net::RequestAnswer& a,
|
||||
Sp<Search>& sr);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "infohash.h"
|
||||
#include "log_enable.h"
|
||||
|
||||
namespace dht {
|
||||
|
||||
class OPENDHT_PUBLIC DhtInterface {
|
||||
public:
|
||||
DhtInterface() = default;
|
||||
virtual ~DhtInterface() = default;
|
||||
|
||||
// [[deprecated]]
|
||||
using Status = NodeStatus;
|
||||
// [[deprecated]]
|
||||
using NodeExport = dht::NodeExport;
|
||||
|
||||
/**
|
||||
* Get the current status of the node for the given family.
|
||||
*/
|
||||
virtual NodeStatus getStatus(sa_family_t af) const = 0;
|
||||
virtual NodeStatus getStatus() const = 0;
|
||||
|
||||
/**
|
||||
* Get the ID of the node.
|
||||
*/
|
||||
virtual const InfoHash& getNodeId() const = 0;
|
||||
|
||||
/**
|
||||
* Performs final operations before quitting.
|
||||
*/
|
||||
virtual void shutdown(ShutdownCallback cb) = 0;
|
||||
|
||||
/**
|
||||
* Returns true if the node is running (have access to an open socket).
|
||||
*
|
||||
* af: address family. If non-zero, will return true if the node
|
||||
* is running for the provided family.
|
||||
*/
|
||||
virtual bool isRunning(sa_family_t af = 0) const = 0;
|
||||
|
||||
virtual void registerType(const ValueType& type) = 0;
|
||||
|
||||
virtual const ValueType& getType(ValueType::Id type_id) const = 0;
|
||||
|
||||
/**
|
||||
* Insert a node in the main routing table.
|
||||
* The node is not pinged, so this should be
|
||||
* used to bootstrap efficiently from previously known nodes.
|
||||
*/
|
||||
virtual void insertNode(const InfoHash& id, const SockAddr&) = 0;
|
||||
virtual void insertNode(const InfoHash& id, const sockaddr* sa, socklen_t salen) = 0;
|
||||
virtual void insertNode(const NodeExport& n) = 0;
|
||||
|
||||
virtual void pingNode(const sockaddr*, socklen_t, DoneCallbackSimple&& cb={}) = 0;
|
||||
|
||||
virtual time_point periodic(const uint8_t *buf, size_t buflen, const SockAddr&) = 0;
|
||||
virtual time_point periodic(const uint8_t *buf, size_t buflen, const sockaddr* from, socklen_t fromlen) = 0;
|
||||
|
||||
/**
|
||||
* Get a value by searching on all available protocols (IPv4, IPv6),
|
||||
* and call the provided get callback when values are found at key.
|
||||
* The operation will start as soon as the node is connected to the network.
|
||||
* @param cb a function called when new values are found on the network.
|
||||
* It should return false to stop the operation.
|
||||
* @param donecb a function called when the operation is complete.
|
||||
cb and donecb won't be called again afterward.
|
||||
* @param f a filter function used to prefilter values.
|
||||
*/
|
||||
virtual void get(const InfoHash& key, GetCallback cb, DoneCallback donecb={}, Value::Filter&& f={}, Where&& w = {}) = 0;
|
||||
virtual void get(const InfoHash& key, GetCallback cb, DoneCallbackSimple donecb={}, Value::Filter&& f={}, Where&& w = {}) = 0;
|
||||
virtual void get(const InfoHash& key, GetCallbackSimple cb, DoneCallback donecb={}, Value::Filter&& f={}, Where&& w = {}) = 0;
|
||||
virtual void get(const InfoHash& key, GetCallbackSimple cb, DoneCallbackSimple donecb, Value::Filter&& f={}, Where&& w = {}) = 0;
|
||||
|
||||
/**
|
||||
* Similar to Dht::get, but sends a Query to filter data remotely.
|
||||
* @param key the key for which to query data for.
|
||||
* @param cb a function called when new values are found on the network.
|
||||
* It should return false to stop the operation.
|
||||
* @param done_cb a function called when the operation is complete.
|
||||
cb and done_cb won't be called again afterward.
|
||||
* @param q a query used to filter values on the remotes before they send a
|
||||
* response.
|
||||
*/
|
||||
virtual void query(const InfoHash& key, QueryCallback cb, DoneCallback done_cb = {}, Query&& q = {}) = 0;
|
||||
virtual void query(const InfoHash& key, QueryCallback cb, DoneCallbackSimple done_cb = {}, Query&& q = {}) = 0;
|
||||
|
||||
/**
|
||||
* Get locally stored data for the given hash.
|
||||
*/
|
||||
virtual std::vector<Sp<Value>> getLocal(const InfoHash& key, Value::Filter f = Value::AllFilter()) const = 0;
|
||||
|
||||
/**
|
||||
* Get locally stored data for the given key and value id.
|
||||
*/
|
||||
virtual Sp<Value> getLocalById(const InfoHash& key, Value::Id vid) const = 0;
|
||||
|
||||
/**
|
||||
* Announce a value on all available protocols (IPv4, IPv6).
|
||||
*
|
||||
* The operation will start as soon as the node is connected to the network.
|
||||
* The done callback will be called once, when the first announce succeeds, or fails.
|
||||
*/
|
||||
virtual void put(const InfoHash& key,
|
||||
Sp<Value>,
|
||||
DoneCallback cb=nullptr,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false) = 0;
|
||||
virtual void put(const InfoHash& key,
|
||||
const Sp<Value>& v,
|
||||
DoneCallbackSimple cb,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false) = 0;
|
||||
virtual void put(const InfoHash& key,
|
||||
Value&& v,
|
||||
DoneCallback cb=nullptr,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false) = 0;
|
||||
virtual void put(const InfoHash& key,
|
||||
Value&& v,
|
||||
DoneCallbackSimple cb,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false) = 0;
|
||||
|
||||
/**
|
||||
* Get data currently being put at the given hash.
|
||||
*/
|
||||
virtual std::vector<Sp<Value>> getPut(const InfoHash&) = 0;
|
||||
|
||||
/**
|
||||
* Get data currently being put at the given hash with the given id.
|
||||
*/
|
||||
virtual Sp<Value> getPut(const InfoHash&, const Value::Id&) = 0;
|
||||
|
||||
/**
|
||||
* Stop any put/announce operation at the given location,
|
||||
* for the value with the given id.
|
||||
*/
|
||||
virtual bool cancelPut(const InfoHash&, const Value::Id&) = 0;
|
||||
|
||||
/**
|
||||
* Listen on the network for any changes involving a specified hash.
|
||||
* The node will register to receive updates from relevent nodes when
|
||||
* new values are added or removed.
|
||||
*
|
||||
* @return a token to cancel the listener later.
|
||||
*/
|
||||
virtual size_t listen(const InfoHash&, GetCallback, Value::Filter={}, Where w = {}) = 0;
|
||||
virtual size_t listen(const InfoHash& key, GetCallbackSimple cb, Value::Filter f={}, Where w = {}) = 0;
|
||||
|
||||
virtual bool cancelListen(const InfoHash&, size_t token) = 0;
|
||||
|
||||
/**
|
||||
* Inform the DHT of lower-layer connectivity changes.
|
||||
* This will cause the DHT to assume a public IP address change.
|
||||
* The DHT will recontact neighbor nodes, re-register for listen ops etc.
|
||||
*/
|
||||
virtual void connectivityChanged(sa_family_t) = 0;
|
||||
virtual void connectivityChanged() = 0;
|
||||
|
||||
/**
|
||||
* Get the list of good nodes for local storage saving purposes
|
||||
* The list is ordered to minimize the back-to-work delay.
|
||||
*/
|
||||
virtual std::vector<NodeExport> exportNodes() = 0;
|
||||
|
||||
virtual std::vector<ValuesExport> exportValues() const = 0;
|
||||
virtual void importValues(const std::vector<ValuesExport>&) = 0;
|
||||
|
||||
virtual NodeStats getNodesStats(sa_family_t af) const = 0;
|
||||
|
||||
virtual std::string getStorageLog() const = 0;
|
||||
virtual std::string getStorageLog(const InfoHash&) const = 0;
|
||||
|
||||
virtual std::string getRoutingTablesLog(sa_family_t) const = 0;
|
||||
virtual std::string getSearchesLog(sa_family_t) const = 0;
|
||||
virtual std::string getSearchLog(const InfoHash&, sa_family_t af = AF_UNSPEC) const = 0;
|
||||
|
||||
virtual void dumpTables() const = 0;
|
||||
virtual std::vector<unsigned> getNodeMessageStats(bool in = false) = 0;
|
||||
|
||||
/**
|
||||
* Set the in-memory storage limit in bytes
|
||||
*/
|
||||
virtual void setStorageLimit(size_t limit = DEFAULT_STORAGE_LIMIT) = 0;
|
||||
|
||||
/**
|
||||
* Returns the total memory usage of stored values and the number
|
||||
* of stored values.
|
||||
*/
|
||||
virtual std::pair<size_t, size_t> getStoreSize() const = 0;
|
||||
|
||||
virtual std::vector<SockAddr> getPublicAddress(sa_family_t family = 0) = 0;
|
||||
|
||||
/**
|
||||
* Enable or disable logging of DHT internal messages
|
||||
*/
|
||||
virtual void setLoggers(LogMethod error = NOLOG, LogMethod warn = NOLOG, LogMethod debug = NOLOG)
|
||||
{
|
||||
DHT_LOG.DEBUG = debug;
|
||||
DHT_LOG.WARN = warn;
|
||||
DHT_LOG.ERR = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only print logs related to the given InfoHash (if given), or disable filter (if zeroes).
|
||||
*/
|
||||
virtual void setLogFilter(const InfoHash& f)
|
||||
{
|
||||
DHT_LOG.setFilter(f);
|
||||
}
|
||||
|
||||
virtual void setPushNotificationToken(const std::string&) {};
|
||||
|
||||
/**
|
||||
* Call linked callback with a push notification
|
||||
* @param notification to process
|
||||
*/
|
||||
virtual void pushNotificationReceived(const std::map<std::string, std::string>& data) = 0;
|
||||
/**
|
||||
* Refresh a listen via a token
|
||||
* @param token
|
||||
*/
|
||||
virtual void resubscribe(unsigned token) = 0;
|
||||
|
||||
protected:
|
||||
bool logFilerEnable_ {};
|
||||
InfoHash logFiler_ {};
|
||||
Logger DHT_LOG;
|
||||
};
|
||||
|
||||
} // namespace dht
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2018 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
* Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include "callbacks.h"
|
||||
#include "def.h"
|
||||
#include "dht_interface.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
namespace restbed {
|
||||
class Request;
|
||||
}
|
||||
|
||||
namespace Json {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace dht {
|
||||
|
||||
class OPENDHT_PUBLIC DhtProxyClient final : public DhtInterface {
|
||||
public:
|
||||
|
||||
DhtProxyClient() : scheduler(DHT_LOG) {}
|
||||
|
||||
explicit DhtProxyClient(std::function<void()> loopSignal, const std::string& serverHost, const std::string& pushClientId = "");
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
virtual void setPushNotificationToken(const std::string& token) {
|
||||
deviceKey_ = token;
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual ~DhtProxyClient();
|
||||
|
||||
/**
|
||||
* Get the ID of the node.
|
||||
*/
|
||||
inline const InfoHash& getNodeId() const { return myid; }
|
||||
|
||||
/**
|
||||
* Get the current status of the node for the given family.
|
||||
*/
|
||||
NodeStatus getStatus(sa_family_t af) const;
|
||||
NodeStatus getStatus() const {
|
||||
return std::max(getStatus(AF_INET), getStatus(AF_INET6));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs final operations before quitting.
|
||||
*/
|
||||
void shutdown(ShutdownCallback cb);
|
||||
|
||||
/**
|
||||
* Returns true if the node is running (have access to an open socket).
|
||||
*
|
||||
* af: address family. If non-zero, will return true if the node
|
||||
* is running for the provided family.
|
||||
*/
|
||||
bool isRunning(sa_family_t af = 0) const;
|
||||
|
||||
/**
|
||||
* Get a value by asking the proxy and call the provided get callback when
|
||||
* values are found at key.
|
||||
* The operation will start as soon as the node is connected to the network.
|
||||
* @param cb a function called when new values are found on the network.
|
||||
* It should return false to stop the operation.
|
||||
* @param donecb a function called when the operation is complete.
|
||||
cb and donecb won't be called again afterward.
|
||||
* @param f a filter function used to prefilter values.
|
||||
*/
|
||||
virtual void get(const InfoHash& key, GetCallback cb, DoneCallback donecb={}, Value::Filter&& f={}, Where&& w = {});
|
||||
virtual void get(const InfoHash& key, GetCallback cb, DoneCallbackSimple donecb={}, Value::Filter&& f={}, Where&& w = {}) {
|
||||
get(key, cb, bindDoneCb(donecb), std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
virtual void get(const InfoHash& key, GetCallbackSimple cb, DoneCallback donecb={}, Value::Filter&& f={}, Where&& w = {}) {
|
||||
get(key, bindGetCb(cb), donecb, std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
virtual void get(const InfoHash& key, GetCallbackSimple cb, DoneCallbackSimple donecb, Value::Filter&& f={}, Where&& w = {}) {
|
||||
get(key, bindGetCb(cb), bindDoneCb(donecb), std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
|
||||
void get(const InfoHash& key, const GetCallback& cb, DoneCallback donecb, const Value::Filter& filterChain);
|
||||
|
||||
/**
|
||||
* Announce a value on all available protocols (IPv4, IPv6).
|
||||
*
|
||||
* The operation will start as soon as the node is connected to the network.
|
||||
* The done callback will be called once, when the first announce succeeds, or fails.
|
||||
* NOTE: For now, created parameter is ignored.
|
||||
*/
|
||||
void put(const InfoHash& key,
|
||||
Sp<Value>,
|
||||
DoneCallback cb=nullptr,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false);
|
||||
void put(const InfoHash& key,
|
||||
const Sp<Value>& v,
|
||||
DoneCallbackSimple cb,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
put(key, v, bindDoneCb(cb), created, permanent);
|
||||
}
|
||||
|
||||
void put(const InfoHash& key,
|
||||
Value&& v,
|
||||
DoneCallback cb=nullptr,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
put(key, std::make_shared<Value>(std::move(v)), cb, created, permanent);
|
||||
}
|
||||
void put(const InfoHash& key,
|
||||
Value&& v,
|
||||
DoneCallbackSimple cb,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
put(key, std::forward<Value>(v), bindDoneCb(cb), created, permanent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param af the socket family
|
||||
* @return node stats from the proxy
|
||||
*/
|
||||
NodeStats getNodesStats(sa_family_t af) const;
|
||||
|
||||
/**
|
||||
* @param family the socket family
|
||||
* @return public address
|
||||
*/
|
||||
std::vector<SockAddr> getPublicAddress(sa_family_t family = 0);
|
||||
|
||||
/**
|
||||
* Listen on the network for any changes involving a specified hash.
|
||||
* The node will register to receive updates from relevent nodes when
|
||||
* new values are added or removed.
|
||||
*
|
||||
* @return a token to cancel the listener later.
|
||||
*/
|
||||
virtual size_t listen(const InfoHash&, GetCallback, Value::Filter={}, Where={});
|
||||
virtual size_t listen(const InfoHash& key, GetCallbackSimple cb, Value::Filter f={}, Where w = {}) {
|
||||
return listen(key, bindGetCb(cb), std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
virtual bool cancelListen(const InfoHash&, size_t token);
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
/**
|
||||
* Call linked callback with a push notification
|
||||
* @param notification to process
|
||||
*/
|
||||
void pushNotificationReceived(const std::map<std::string, std::string>& notification);
|
||||
/**
|
||||
* Refresh a listen via a token
|
||||
* @param token
|
||||
*/
|
||||
void resubscribe(const unsigned token);
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
time_point periodic(const uint8_t*, size_t, const SockAddr&);
|
||||
time_point periodic(const uint8_t *buf, size_t buflen, const sockaddr* from, socklen_t fromlen) {
|
||||
return periodic(buf, buflen, SockAddr(from, fromlen));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Similar to Dht::get, but sends a Query to filter data remotely.
|
||||
* @param key the key for which to query data for.
|
||||
* @param cb a function called when new values are found on the network.
|
||||
* It should return false to stop the operation.
|
||||
* @param done_cb a function called when the operation is complete.
|
||||
cb and done_cb won't be called again afterward.
|
||||
* @param q a query used to filter values on the remotes before they send a
|
||||
* response.
|
||||
*/
|
||||
virtual void query(const InfoHash& /*key*/, QueryCallback /*cb*/, DoneCallback /*done_cb*/ = {}, Query&& /*q*/ = {}) { }
|
||||
virtual void query(const InfoHash& key, QueryCallback cb, DoneCallbackSimple done_cb = {}, Query&& q = {}) {
|
||||
query(key, cb, bindDoneCb(done_cb), std::forward<Query>(q));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data currently being put at the given hash.
|
||||
*/
|
||||
std::vector<Sp<Value>> getPut(const InfoHash&) { return {}; }
|
||||
|
||||
/**
|
||||
* Get data currently being put at the given hash with the given id.
|
||||
*/
|
||||
Sp<Value> getPut(const InfoHash&, const Value::Id&) { return {}; }
|
||||
|
||||
/**
|
||||
* Stop any put/announce operation at the given location,
|
||||
* for the value with the given id.
|
||||
*/
|
||||
bool cancelPut(const InfoHash&, const Value::Id&) { return false; }
|
||||
|
||||
void pingNode(const sockaddr*, socklen_t, DoneCallbackSimple&& /*cb*/={}) { }
|
||||
|
||||
/**
|
||||
* NOTE: The following methods will not be implemented because the
|
||||
* DhtProxyClient doesn't have any storage nor synchronization process
|
||||
*/
|
||||
void insertNode(const InfoHash&, const SockAddr&) { }
|
||||
void insertNode(const InfoHash&, const sockaddr*, socklen_t) { }
|
||||
void insertNode(const NodeExport&) { }
|
||||
std::pair<size_t, size_t> getStoreSize() const { return {}; }
|
||||
virtual void registerType(const ValueType&) { }
|
||||
const ValueType& getType(ValueType::Id) const { return NO_VALUE; }
|
||||
std::vector<Sp<Value>> getLocal(const InfoHash&, Value::Filter) const { return {}; }
|
||||
Sp<Value> getLocalById(const InfoHash&, Value::Id) const { return {}; }
|
||||
std::vector<NodeExport> exportNodes() { return {}; }
|
||||
std::vector<ValuesExport> exportValues() const { return {}; }
|
||||
void importValues(const std::vector<ValuesExport>&) {}
|
||||
std::string getStorageLog() const { return {}; }
|
||||
std::string getStorageLog(const InfoHash&) const { return {}; }
|
||||
std::string getRoutingTablesLog(sa_family_t) const { return {}; }
|
||||
std::string getSearchesLog(sa_family_t) const { return {}; }
|
||||
std::string getSearchLog(const InfoHash&, sa_family_t) const { return {}; }
|
||||
void dumpTables() const {}
|
||||
std::vector<unsigned> getNodeMessageStats(bool) { return {}; }
|
||||
void setStorageLimit(size_t) {}
|
||||
void connectivityChanged(sa_family_t) {}
|
||||
void connectivityChanged() { }
|
||||
|
||||
private:
|
||||
const ValueType NO_VALUE;
|
||||
|
||||
/**
|
||||
* Start the connection with a server.
|
||||
*/
|
||||
void startProxy();
|
||||
|
||||
/**
|
||||
* Get informations from the proxy node
|
||||
* @return the JSON returned by the proxy
|
||||
*/
|
||||
void getProxyInfos();
|
||||
void onProxyInfos(const Json::Value& val);
|
||||
SockAddr parsePublicAddress(const Json::Value& val);
|
||||
|
||||
void opFailed();
|
||||
|
||||
/**
|
||||
* Initialize statusIpvX_
|
||||
*/
|
||||
void getConnectivityStatus();
|
||||
/**
|
||||
* cancel all Listeners
|
||||
*/
|
||||
void cancelAllListeners();
|
||||
/**
|
||||
* cancel all Operations
|
||||
*/
|
||||
void cancelAllOperations();
|
||||
std::string serverHost_;
|
||||
std::string pushClientId_;
|
||||
|
||||
std::atomic_flag ongoingStatusUpdate_ = ATOMIC_FLAG_INIT;
|
||||
NodeStatus statusIpv4_ {NodeStatus::Disconnected};
|
||||
NodeStatus statusIpv6_ {NodeStatus::Disconnected};
|
||||
NodeStats stats4_ {};
|
||||
NodeStats stats6_ {};
|
||||
SockAddr publicAddress_;
|
||||
|
||||
InfoHash myid {};
|
||||
|
||||
/**
|
||||
* Store listen requests.
|
||||
*/
|
||||
struct Listener
|
||||
{
|
||||
size_t token;
|
||||
std::shared_ptr<restbed::Request> req;
|
||||
std::string key;
|
||||
GetCallback cb;
|
||||
Value::Filter filterChain;
|
||||
std::thread thread;
|
||||
std::shared_ptr<unsigned> pushNotifToken; // NOTE: unused if not using push notifications
|
||||
};
|
||||
std::vector<Listener> listeners_;
|
||||
size_t listener_token_ {0};
|
||||
std::mutex lockListener_;
|
||||
|
||||
/**
|
||||
* Store current put and get requests.
|
||||
*/
|
||||
struct Operation
|
||||
{
|
||||
std::shared_ptr<restbed::Request> req;
|
||||
std::thread thread;
|
||||
std::shared_ptr<std::atomic_bool> finished;
|
||||
};
|
||||
std::vector<Operation> operations_;
|
||||
std::mutex lockOperations_;
|
||||
/**
|
||||
* Callbacks should be executed in the main thread.
|
||||
*/
|
||||
std::vector<std::function<void()>> callbacks_;
|
||||
std::mutex lockCallbacks;
|
||||
|
||||
std::thread statusThread_;
|
||||
mutable std::mutex lockCurrentProxyInfos_;
|
||||
|
||||
Scheduler scheduler;
|
||||
/**
|
||||
* Retrieve if we can connect to the proxy (update statusIpvX_)
|
||||
*/
|
||||
void confirmProxy();
|
||||
Sp<Scheduler::Job> nextProxyConfirmation {};
|
||||
/**
|
||||
* Relaunch LISTEN requests if the client disconnect/reconnect.
|
||||
*/
|
||||
void restartListeners();
|
||||
|
||||
/**
|
||||
* If we want to use push notifications by default.
|
||||
* NOTE: empty by default to avoid to use services like FCM or APN.
|
||||
*/
|
||||
std::string deviceKey_ {};
|
||||
unsigned callbackId_ {0};
|
||||
std::mutex lockCallback_;
|
||||
|
||||
const std::function<void()> loopSignal_;
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
void fillBodyToGetToken(std::shared_ptr<restbed::Request> request);
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OPENDHT_PROXY_CLIENT
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 2017-2018 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "def.h"
|
||||
#include "sockaddr.h"
|
||||
#include "infohash.h"
|
||||
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <restbed>
|
||||
|
||||
namespace Json {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace dht {
|
||||
|
||||
class DhtRunner;
|
||||
|
||||
/**
|
||||
* Describes the REST API
|
||||
*/
|
||||
class OPENDHT_PUBLIC DhtProxyServer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Start the Http server for OpenDHT
|
||||
* @param dht the DhtRunner linked to this proxy server
|
||||
* @param port to listen
|
||||
* @param pushServer where to push notifications
|
||||
* @note if the server fails to start (if port is already used or reserved),
|
||||
* it will fails silently
|
||||
*/
|
||||
DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port = 8000, const std::string& pushServer = "");
|
||||
virtual ~DhtProxyServer();
|
||||
|
||||
DhtProxyServer(const DhtProxyServer& other) = delete;
|
||||
DhtProxyServer(DhtProxyServer&& other) = delete;
|
||||
DhtProxyServer& operator=(const DhtProxyServer& other) = delete;
|
||||
DhtProxyServer& operator=(DhtProxyServer&& other) = delete;
|
||||
|
||||
/**
|
||||
* Stop the DhtProxyServer
|
||||
*/
|
||||
void stop();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return the PublicKey id, the node id and node stats
|
||||
* Method: GET "/"
|
||||
* Result: HTTP 200, body: Node infos in JSON format
|
||||
* On error: HTTP 503, body: {"err":"xxxx"}
|
||||
* @param session
|
||||
*/
|
||||
void getNodeInfo(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
/**
|
||||
* Return Values of an infoHash
|
||||
* Method: GET "/{InfoHash: .*}"
|
||||
* Return: Multiple JSON object in parts. Example:
|
||||
* Value in JSON format\n
|
||||
* Value in JSON format
|
||||
*
|
||||
* On error: HTTP 503, body: {"err":"xxxx"}
|
||||
* @param session
|
||||
*/
|
||||
void get(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
/**
|
||||
* Listen incoming Values of an infoHash.
|
||||
* Method: LISTEN "/{InfoHash: .*}"
|
||||
* Return: Multiple JSON object in parts. Example:
|
||||
* Value in JSON format\n
|
||||
* Value in JSON format
|
||||
*
|
||||
* On error: HTTP 503, body: {"err":"xxxx"}
|
||||
* @param session
|
||||
*/
|
||||
void listen(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
/**
|
||||
* Put a value on the DHT
|
||||
* Method: POST "/{InfoHash: .*}"
|
||||
* body = Value to put in JSON
|
||||
* Return: HTTP 200 if success and the value put in JSON
|
||||
* On error: HTTP 503, body: {"err":"xxxx"} if no dht
|
||||
* HTTP 400, body: {"err":"xxxx"} if bad json or HTTP 502 if put fails
|
||||
* @param session
|
||||
*/
|
||||
void put(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
#if OPENDHT_PROXY_SERVER_IDENTITY
|
||||
/**
|
||||
* Put a value to sign by the proxy on the DHT
|
||||
* Method: SIGN "/{InfoHash: .*}"
|
||||
* body = Value to put in JSON
|
||||
* Return: HTTP 200 if success and the value put in JSON
|
||||
* On error: HTTP 503, body: {"err":"xxxx"} if no dht
|
||||
* HTTP 400, body: {"err":"xxxx"} if bad json
|
||||
* @param session
|
||||
*/
|
||||
void putSigned(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
/**
|
||||
* Put a value to encrypt by the proxy on the DHT
|
||||
* Method: ENCRYPT "/{hash: .*}"
|
||||
* body = Value to put in JSON + "to":"infoHash"
|
||||
* Return: HTTP 200 if success and the value put in JSON
|
||||
* On error: HTTP 503, body: {"err":"xxxx"} if no dht
|
||||
* HTTP 400, body: {"err":"xxxx"} if bad json
|
||||
* @param session
|
||||
*/
|
||||
void putEncrypted(const std::shared_ptr<restbed::Session>& session) const;
|
||||
#endif // OPENDHT_PROXY_SERVER_IDENTITY
|
||||
|
||||
/**
|
||||
* Return Values of an infoHash filtered by a value id
|
||||
* Method: GET "/{InfoHash: .*}/{ValueId: .*}"
|
||||
* Return: Multiple JSON object in parts. Example:
|
||||
* Value in JSON format\n
|
||||
* Value in JSON format
|
||||
*
|
||||
* On error: HTTP 503, body: {"err":"xxxx"}
|
||||
* @param session
|
||||
*/
|
||||
void getFiltered(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
/**
|
||||
* Respond allowed Methods
|
||||
* Method: OPTIONS "/{hash: .*}"
|
||||
* Return: HTTP 200 + Allow: allowed methods
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
|
||||
* @param session
|
||||
*/
|
||||
void handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const;
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
/**
|
||||
* Subscribe to push notifications for an iOS or Android device.
|
||||
* Method: SUBSCRIBE "/{InfoHash: .*}"
|
||||
* Body: {"key": "device_key", (optional)"callback_id":y,
|
||||
* (optional)"isAndroid":false (default true)}"
|
||||
* Return: {"token": x}" where x if a token to save
|
||||
* @note: the listen will timeout after six hours (and send a push notification).
|
||||
* so you need to refresh the operation each six hours.
|
||||
* @note: callback_id is used to add the possibility to have multiple listen
|
||||
* on same hash for same device and must be > 0
|
||||
* @param session
|
||||
*/
|
||||
void subscribe(const std::shared_ptr<restbed::Session>& session) const;
|
||||
/**
|
||||
* Unsubscribe to push notifications for an iOS or Android device.
|
||||
* Method: UNSUBSCRIBE "/{InfoHash: .*}"
|
||||
* Body: {"key": "device_key", "token": x, (optional)"callback_id":y"
|
||||
* where x if the token to cancel
|
||||
* Return: nothing
|
||||
* @note: callback id is used to add the possibility to have multiple listen
|
||||
* on same hash for same device
|
||||
* @param session
|
||||
*/
|
||||
void unsubscribe(const std::shared_ptr<restbed::Session>& session) const;
|
||||
/**
|
||||
* Send a push notification via a gorush push gateway
|
||||
* @param key of the device
|
||||
* @param json, the content to send
|
||||
*/
|
||||
void sendPushNotification(const std::string& key, const Json::Value& json, bool isAndroid) const;
|
||||
#endif //OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
std::thread server_thread {};
|
||||
std::unique_ptr<restbed::Service> service_;
|
||||
std::shared_ptr<DhtRunner> dht_;
|
||||
|
||||
// Handle client quit for listen.
|
||||
// NOTE: can be simplified when we will supports restbed 5.0
|
||||
std::thread listenThread_;
|
||||
struct SessionToHashToken {
|
||||
std::shared_ptr<restbed::Session> session;
|
||||
InfoHash hash;
|
||||
std::future<size_t> token;
|
||||
};
|
||||
mutable std::vector<SessionToHashToken> currentListeners_;
|
||||
mutable std::mutex lockListener_;
|
||||
std::atomic_bool stopListeners {false};
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
struct PushListener {
|
||||
std::string pushToken;
|
||||
InfoHash hash;
|
||||
unsigned token;
|
||||
std::future<size_t> internalToken;
|
||||
std::chrono::steady_clock::time_point deadline;
|
||||
bool started {false};
|
||||
unsigned callbackId {0};
|
||||
std::string clientId {};
|
||||
bool isAndroid {true};
|
||||
};
|
||||
mutable std::mutex lockPushListeners_;
|
||||
mutable std::vector<PushListener> pushListeners_;
|
||||
mutable unsigned tokenPushNotif_ {0};
|
||||
const std::string pushServer_;
|
||||
#endif //OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
/**
|
||||
* Remove finished listeners
|
||||
* @param testSession if we remove the listener only if the session is closed
|
||||
*/
|
||||
void removeClosedListeners(bool testSession = true);
|
||||
/**
|
||||
* Launch or remove push listeners if needed
|
||||
*/
|
||||
void handlePushListeners();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
+101
-17
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +20,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include "securedht.h"
|
||||
#include "infohash.h"
|
||||
#include "value.h"
|
||||
#include "callbacks.h"
|
||||
@@ -53,6 +53,13 @@ class OPENDHT_PUBLIC DhtRunner {
|
||||
public:
|
||||
typedef std::function<void(NodeStatus, NodeStatus)> StatusCallback;
|
||||
|
||||
struct Config {
|
||||
SecureDhtConfig dht_config;
|
||||
bool threaded;
|
||||
std::string proxy_server;
|
||||
std::string push_node_id;
|
||||
};
|
||||
|
||||
DhtRunner();
|
||||
virtual ~DhtRunner();
|
||||
|
||||
@@ -202,7 +209,7 @@ public:
|
||||
* Insert known nodes to the routing table, without necessarly ping them.
|
||||
* Usefull to restart a node and get things running fast without putting load on the network.
|
||||
*/
|
||||
void bootstrap(const std::vector<std::pair<sockaddr_storage, socklen_t>>& nodes, DoneCallbackSimple&& cb={});
|
||||
void bootstrap(const std::vector<SockAddr>& nodes, DoneCallbackSimple&& cb={});
|
||||
void bootstrap(const SockAddr& addr, DoneCallbackSimple&& cb={});
|
||||
|
||||
/**
|
||||
@@ -250,7 +257,7 @@ public:
|
||||
* @param f: address family of the bound port to retreive.
|
||||
*/
|
||||
in_port_t getBoundPort(sa_family_t f = AF_INET) const {
|
||||
return ntohs(((sockaddr_in*)&getBound(f).first)->sin_port);
|
||||
return getBound(f).getPort();
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> getStoreSize() const;
|
||||
@@ -294,11 +301,6 @@ public:
|
||||
void registerCertificate(std::shared_ptr<crypto::Certificate> cert);
|
||||
void setLocalCertificateStore(CertificateStoreQuery&& query_method);
|
||||
|
||||
struct Config {
|
||||
SecureDhtConfig dht_config;
|
||||
bool threaded;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param port: Local port to bind. Both IPv4 and IPv6 will be tried (ANY).
|
||||
* @param identity: RSA key pair to use for cryptographic operations.
|
||||
@@ -316,7 +318,9 @@ public:
|
||||
},
|
||||
/*.id = */identity
|
||||
},
|
||||
/*.threaded = */threaded
|
||||
/*.threaded = */threaded,
|
||||
/*.proxy_server = */"",
|
||||
/*.push_node_id = */""
|
||||
});
|
||||
}
|
||||
void run(in_port_t port, Config config);
|
||||
@@ -329,7 +333,7 @@ public:
|
||||
* @param threaded: If false, loop() must be called periodically. Otherwise a thread is launched.
|
||||
* @param cb: Optional callback to receive general state information.
|
||||
*/
|
||||
void run(const sockaddr_in* local4, const sockaddr_in6* local6, Config config);
|
||||
void run(const SockAddr& local4, const SockAddr& local6, Config config);
|
||||
|
||||
/**
|
||||
* Same as @run(sockaddr_in, sockaddr_in6, Identity, bool, StatusCallback), but with string IP addresses and service (port).
|
||||
@@ -347,7 +351,13 @@ public:
|
||||
*/
|
||||
time_point loop() {
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return loop_();
|
||||
time_point wakeup = time_point::min();
|
||||
try {
|
||||
wakeup = loop_();
|
||||
} catch (const dht::SocketException& e) {
|
||||
startNetwork(bound4, bound6);
|
||||
}
|
||||
return wakeup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -362,6 +372,43 @@ public:
|
||||
*/
|
||||
void join();
|
||||
|
||||
void setProxyServer(const std::string& proxy, const std::string& pushNodeId = "") {
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (config_.proxy_server == proxy and config_.push_node_id == pushNodeId)
|
||||
return;
|
||||
config_.proxy_server = proxy;
|
||||
config_.push_node_id = pushNodeId;
|
||||
enableProxy(use_proxy and not config_.proxy_server.empty());
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Start or stop the proxy
|
||||
* @param proxify if we want to use the proxy
|
||||
* @param deviceKey non empty to enable push notifications
|
||||
*/
|
||||
void enableProxy(bool proxify);
|
||||
|
||||
/* Push notification methods */
|
||||
|
||||
/**
|
||||
* Updates the push notification device token
|
||||
*/
|
||||
void setPushNotificationToken(const std::string& token);
|
||||
|
||||
/**
|
||||
* Insert a push notification to process for OpenDHT
|
||||
*/
|
||||
void pushNotificationReceived(const std::map<std::string, std::string>& data) const;
|
||||
/**
|
||||
* Refresh a listen via a token
|
||||
* @param token
|
||||
*/
|
||||
void resubscribe(unsigned token);
|
||||
|
||||
/* Proxy server mothods */
|
||||
void forwardAllMessages(bool forward);
|
||||
|
||||
private:
|
||||
static constexpr std::chrono::seconds BOOTSTRAP_PERIOD {10};
|
||||
|
||||
@@ -373,16 +420,48 @@ private:
|
||||
*/
|
||||
void tryBootstrapContinuously();
|
||||
|
||||
void doRun(const sockaddr_in* sin4, const sockaddr_in6* sin6, SecureDhtConfig config);
|
||||
void startNetwork(const SockAddr sin4, const SockAddr sin6);
|
||||
time_point loop_();
|
||||
|
||||
static std::vector<std::pair<sockaddr_storage, socklen_t>> getAddrInfo(const std::string& host, const std::string& service);
|
||||
|
||||
NodeStatus getStatus() const {
|
||||
return std::max(status4, status6);
|
||||
}
|
||||
|
||||
/** Local DHT instance */
|
||||
std::unique_ptr<SecureDht> dht_;
|
||||
|
||||
/** Proxy client instance */
|
||||
std::unique_ptr<SecureDht> dht_via_proxy_;
|
||||
|
||||
/** true if we are currently using a proxy */
|
||||
std::atomic_bool use_proxy {false};
|
||||
|
||||
/** Current configuration */
|
||||
Config config_;
|
||||
|
||||
/**
|
||||
* reset dht clients
|
||||
*/
|
||||
void resetDht();
|
||||
/**
|
||||
* @return the current active DHT
|
||||
*/
|
||||
SecureDht* activeDht() const;
|
||||
|
||||
/**
|
||||
* Store current listeners and translates global tokens for each client.
|
||||
*/
|
||||
struct Listener {
|
||||
size_t tokenClassicDht;
|
||||
size_t tokenProxyDht;
|
||||
GetCallback gcb;
|
||||
InfoHash hash;
|
||||
Value::Filter f;
|
||||
Where w;
|
||||
};
|
||||
std::map<size_t, Listener> listeners_ {};
|
||||
size_t listener_token_ {1};
|
||||
|
||||
mutable std::mutex dht_mtx {};
|
||||
std::thread dht_thread {};
|
||||
std::condition_variable cv {};
|
||||
@@ -405,14 +484,19 @@ private:
|
||||
std::queue<std::function<void(SecureDht&)>> pending_ops {};
|
||||
std::mutex storage_mtx {};
|
||||
|
||||
std::atomic<bool> running {false};
|
||||
std::atomic_bool running {false};
|
||||
std::atomic_bool running_network {false};
|
||||
|
||||
NodeStatus status4 {NodeStatus::Disconnected},
|
||||
status6 {NodeStatus::Disconnected};
|
||||
StatusCallback statusCb {nullptr};
|
||||
|
||||
int s4 {-1}, s6 {-1};
|
||||
SockAddr bound4 {};
|
||||
SockAddr bound6 {};
|
||||
|
||||
/** Push notification token */
|
||||
std::string pushToken_;
|
||||
};
|
||||
|
||||
}
|
||||
+214
-138
@@ -19,6 +19,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "def.h"
|
||||
#include "rng.h"
|
||||
|
||||
#include <msgpack.hpp>
|
||||
|
||||
@@ -44,133 +45,99 @@ typedef uint16_t in_port_t;
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
// bytes
|
||||
#define HASH_LEN 20u
|
||||
|
||||
namespace dht {
|
||||
|
||||
using byte = uint8_t;
|
||||
|
||||
template <size_t N>
|
||||
class OPENDHT_PUBLIC Hash
|
||||
{
|
||||
public:
|
||||
Hash() {
|
||||
data_.fill(0);
|
||||
}
|
||||
Hash(const msgpack::object& o) {
|
||||
msgpack_unpack(o);
|
||||
}
|
||||
byte* data() {
|
||||
return data_.data();
|
||||
}
|
||||
const byte* data() const {
|
||||
return data_.data();
|
||||
}
|
||||
constexpr size_t size() const { return data_.size(); }
|
||||
explicit operator bool() const { return std::any_of(data_.begin(), data_.end(), [](byte _b) { return _b != 0; }); }
|
||||
bool operator==(Hash const& _c) const { return data_ == _c.data_; }
|
||||
bool operator!=(Hash const& _c) const { return data_ != _c.data_; }
|
||||
bool operator<(Hash const& _c) const { for (unsigned i = 0; i < N; ++i) if (data_[i] < _c.data_[i]) return true; else if (data_[i] > _c.data_[i]) return false; return false; }
|
||||
bool operator>=(Hash const& _c) const { return !operator<(_c); }
|
||||
bool operator<=(Hash const& _c) const { return operator==(_c) || operator<(_c); }
|
||||
bool operator>(Hash const& _c) const { return !operator<=(_c); }
|
||||
template <typename Packer>
|
||||
void msgpack_pack(Packer& pk) const
|
||||
{
|
||||
pk.pack_bin(N);
|
||||
pk.pack_bin_body((char*)data_.data(), N);
|
||||
}
|
||||
void msgpack_unpack(msgpack::object o) {
|
||||
if (o.type != msgpack::type::BIN or o.via.bin.size != N)
|
||||
throw msgpack::type_error();
|
||||
std::copy_n(o.via.bin.ptr, N, data_.data());
|
||||
}
|
||||
|
||||
template <size_t M>
|
||||
OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const Hash<M>& h);
|
||||
|
||||
template <size_t M>
|
||||
OPENDHT_PUBLIC friend std::istream& operator>> (std::istream& s, Hash<M>& h);
|
||||
|
||||
std::string toString() const {
|
||||
std::stringstream ss;
|
||||
ss << *this;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<byte, N> data_;
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
std::ostream& operator<< (std::ostream& s, const Hash<N>& h)
|
||||
{
|
||||
s << std::hex;
|
||||
for (unsigned i=0; i<h.size(); i++)
|
||||
s << std::setfill('0') << std::setw(2) << (unsigned)h.data_[i];
|
||||
s << std::dec;
|
||||
return s;
|
||||
namespace crypto {
|
||||
OPENDHT_PUBLIC void hash(const uint8_t* data, size_t data_length, uint8_t* hash, size_t hash_length);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::istream& operator>> (std::istream& s, Hash<N>& h)
|
||||
{
|
||||
std::array<char, h.size()*2> dat;
|
||||
s.exceptions(std::istream::eofbit | std::istream::failbit);
|
||||
s.read(&(*dat.begin()), dat.size());
|
||||
for (size_t i = 0; i < h.size(); i++) {
|
||||
unsigned res = 0;
|
||||
sscanf(dat.data() + 2*i, "%02x", &res);
|
||||
h[i] = res;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
using h256 = Hash<32>;
|
||||
using PkId = h256;
|
||||
|
||||
/**
|
||||
* Represents an InfoHash.
|
||||
* An InfoHash is a byte array of HASH_LEN bytes.
|
||||
* InfoHashes identify nodes and values in the Dht.
|
||||
*/
|
||||
class OPENDHT_PUBLIC InfoHash final : public std::array<uint8_t, HASH_LEN> {
|
||||
template <size_t N>
|
||||
class OPENDHT_PUBLIC Hash {
|
||||
public:
|
||||
constexpr InfoHash() : std::array<uint8_t, HASH_LEN>() {}
|
||||
constexpr InfoHash(const std::array<uint8_t, HASH_LEN>& h) : std::array<uint8_t, HASH_LEN>(h) {}
|
||||
InfoHash(const uint8_t* h, size_t h_len) : std::array<uint8_t, HASH_LEN>() {
|
||||
if (h_len < HASH_LEN)
|
||||
fill(0);
|
||||
else
|
||||
std::copy_n(h, HASH_LEN, begin());
|
||||
}
|
||||
using T = std::array<uint8_t, N>;
|
||||
typedef typename T::iterator iterator;
|
||||
typedef typename T::const_iterator const_iterator;
|
||||
|
||||
Hash () {
|
||||
data_.fill(0);
|
||||
}
|
||||
Hash (const uint8_t* h, size_t data_len) {
|
||||
if (data_len < N)
|
||||
data_.fill(0);
|
||||
else
|
||||
std::copy_n(h, N, data_.begin());
|
||||
}
|
||||
/**
|
||||
* Constructor from an hexadecimal string (without "0x").
|
||||
* hex must be at least 2.HASH_LEN characters long.
|
||||
* If too long, only the first 2.HASH_LEN characters are read.
|
||||
*/
|
||||
explicit InfoHash(const std::string& hex);
|
||||
explicit Hash(const std::string& hex);
|
||||
|
||||
InfoHash(const msgpack::object& o) {
|
||||
Hash(const msgpack::object& o) {
|
||||
msgpack_unpack(o);
|
||||
}
|
||||
|
||||
size_t size() const { return data_.size(); }
|
||||
const uint8_t* data() const { return data_.data(); }
|
||||
uint8_t* data() { return data_.data(); }
|
||||
iterator begin() { return data_.begin(); }
|
||||
const_iterator cbegin() const { return data_.cbegin(); }
|
||||
iterator end() { return data_.end(); }
|
||||
const_iterator cend() const { return data_.cend(); }
|
||||
|
||||
bool operator==(const Hash& h) const {
|
||||
auto a = reinterpret_cast<const uint32_t*>(data_.data());
|
||||
auto b = reinterpret_cast<const uint32_t*>(h.data_.data());
|
||||
constexpr unsigned n = N / sizeof(uint32_t);
|
||||
for (unsigned i=0; i < n; i++)
|
||||
if (a[i] != b[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool operator!=(const Hash& h) const { return !(*this == h); }
|
||||
|
||||
bool operator<(const Hash& o) const {
|
||||
for(unsigned i = 0; i < N; i++) {
|
||||
if(data_[i] != o.data_[i])
|
||||
return data_[i] < o.data_[i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
auto a = reinterpret_cast<const uint32_t*>(data_.data());
|
||||
auto b = reinterpret_cast<const uint32_t*>(data_.data() + N);
|
||||
for (; a != b; a++) {
|
||||
if (*a)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t& operator[](size_t index) { return data_[index]; }
|
||||
const uint8_t& operator[](size_t index) const { return data_[index]; }
|
||||
|
||||
/**
|
||||
* Find the lowest 1 bit in an id.
|
||||
* Result will allways be lower than 8*HASH_LEN
|
||||
* Result will allways be lower than 8*N
|
||||
*/
|
||||
inline unsigned lowbit() const {
|
||||
int i, j;
|
||||
for(i = HASH_LEN-1; i >= 0; i--)
|
||||
if((*this)[i] != 0)
|
||||
for(i = N-1; i >= 0; i--)
|
||||
if(data_[i] != 0)
|
||||
break;
|
||||
if(i < 0)
|
||||
return -1;
|
||||
for(j = 7; j >= 0; j--)
|
||||
if(((*this)[i] & (0x80 >> j)) != 0)
|
||||
if((data_[i] & (0x80 >> j)) != 0)
|
||||
break;
|
||||
return 8 * i + j;
|
||||
}
|
||||
@@ -179,25 +146,25 @@ public:
|
||||
* Forget about the ``XOR-metric''. An id is just a path from the
|
||||
* root of the tree, so bits are numbered from the start.
|
||||
*/
|
||||
static inline int cmp(const InfoHash& id1, const InfoHash& id2) {
|
||||
return std::memcmp(id1.data(), id2.data(), HASH_LEN);
|
||||
static inline int cmp(const Hash& id1, const Hash& id2) {
|
||||
return std::memcmp(id1.data_.data(), id2.data_.data(), N);
|
||||
}
|
||||
|
||||
/** Find how many bits two ids have in common. */
|
||||
static inline unsigned
|
||||
commonBits(const InfoHash& id1, const InfoHash& id2)
|
||||
commonBits(const Hash& id1, const Hash& id2)
|
||||
{
|
||||
unsigned i, j;
|
||||
uint8_t x;
|
||||
for(i = 0; i < HASH_LEN; i++) {
|
||||
if(id1[i] != id2[i])
|
||||
for(i = 0; i < N; i++) {
|
||||
if(id1.data_[i] != id2.data_[i])
|
||||
break;
|
||||
}
|
||||
|
||||
if(i == HASH_LEN)
|
||||
return 8*HASH_LEN;
|
||||
if(i == N)
|
||||
return 8*N;
|
||||
|
||||
x = id1[i] ^ id2[i];
|
||||
x = id1.data_[i] ^ id2.data_[i];
|
||||
|
||||
j = 0;
|
||||
while((x & 0x80) == 0) {
|
||||
@@ -210,14 +177,14 @@ public:
|
||||
|
||||
/** Determine whether id1 or id2 is closer to this */
|
||||
int
|
||||
xorCmp(const InfoHash& id1, const InfoHash& id2) const
|
||||
xorCmp(const Hash& id1, const Hash& id2) const
|
||||
{
|
||||
for(unsigned i = 0; i < HASH_LEN; i++) {
|
||||
for(unsigned i = 0; i < N; i++) {
|
||||
uint8_t xor1, xor2;
|
||||
if(id1[i] == id2[i])
|
||||
if(id1.data_[i] == id2.data_[i])
|
||||
continue;
|
||||
xor1 = id1[i] ^ (*this)[i];
|
||||
xor2 = id2[i] ^ (*this)[i];
|
||||
xor1 = id1.data_[i] ^ data_[i];
|
||||
xor2 = id2.data_[i] ^ data_[i];
|
||||
if(xor1 < xor2)
|
||||
return -1;
|
||||
else
|
||||
@@ -229,7 +196,7 @@ public:
|
||||
bool
|
||||
getBit(unsigned nbit) const
|
||||
{
|
||||
auto& num = *(cbegin()+(nbit/8));
|
||||
auto& num = *(data_.cbegin()+(nbit/8));
|
||||
unsigned bit = 7 - (nbit % 8);
|
||||
return (num >> bit) & 1;
|
||||
}
|
||||
@@ -237,46 +204,44 @@ public:
|
||||
void
|
||||
setBit(unsigned nbit, bool b)
|
||||
{
|
||||
auto& num = (*this)[nbit/8];
|
||||
auto& num = data_[nbit/8];
|
||||
unsigned bit = 7 - (nbit % 8);
|
||||
num ^= (-b ^ num) & (1 << bit);
|
||||
}
|
||||
|
||||
double
|
||||
toFloat() const
|
||||
{
|
||||
double toFloat() const {
|
||||
using D = size_t;
|
||||
double v = 0.;
|
||||
for (unsigned i = 0; i < std::min<size_t>(HASH_LEN, sizeof(unsigned)-1); i++)
|
||||
v += *(cbegin()+i)/(double)(1<<(8*(i+1)));
|
||||
for (size_t i = 0; i < std::min<size_t>(N, sizeof(D)-1); i++)
|
||||
v += *(data_.cbegin()+i) / (double)((D)1 << 8*(i+1));
|
||||
return v;
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(const InfoHash& o) const {
|
||||
for(unsigned i = 0; i < HASH_LEN; i++) {
|
||||
if((*this)[i] != o[i])
|
||||
return (*this)[i] < o[i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline InfoHash get(const std::string& data) {
|
||||
static inline Hash get(const std::string& data) {
|
||||
return get((const uint8_t*)data.data(), data.size());
|
||||
}
|
||||
|
||||
static inline InfoHash get(const std::vector<uint8_t>& data) {
|
||||
static inline Hash get(const std::vector<uint8_t>& data) {
|
||||
return get(data.data(), data.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hash from a given data buffer of size data_len.
|
||||
*/
|
||||
static InfoHash get(const uint8_t* data, size_t data_len);
|
||||
static Hash get(const uint8_t* data, size_t data_len)
|
||||
{
|
||||
Hash ret;
|
||||
crypto::hash(data, data_len, ret.data(), N);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static InfoHash getRandom();
|
||||
static Hash getRandom();
|
||||
|
||||
OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const InfoHash& h);
|
||||
OPENDHT_PUBLIC friend std::istream& operator>> (std::istream& s, InfoHash& h);
|
||||
template <size_t M>
|
||||
OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const Hash<M>& h);
|
||||
|
||||
template <size_t M>
|
||||
OPENDHT_PUBLIC friend std::istream& operator>> (std::istream& s, Hash<M>& h);
|
||||
|
||||
const char* to_c_str() const;
|
||||
|
||||
@@ -285,24 +250,135 @@ public:
|
||||
template <typename Packer>
|
||||
void msgpack_pack(Packer& pk) const
|
||||
{
|
||||
pk.pack_bin(HASH_LEN);
|
||||
pk.pack_bin_body((char*)data(), HASH_LEN);
|
||||
pk.pack_bin(N);
|
||||
pk.pack_bin_body((char*)data_.data(), N);
|
||||
}
|
||||
|
||||
void msgpack_unpack(msgpack::object o) {
|
||||
if (o.type != msgpack::type::BIN or o.via.bin.size != HASH_LEN)
|
||||
if (o.type != msgpack::type::BIN or o.via.bin.size != N)
|
||||
throw msgpack::type_error();
|
||||
std::copy_n(o.via.bin.ptr, HASH_LEN, data());
|
||||
std::copy_n(o.via.bin.ptr, N, data_.data());
|
||||
}
|
||||
|
||||
private:
|
||||
T data_;
|
||||
void fromString(const char*);
|
||||
};
|
||||
|
||||
static constexpr const InfoHash zeroes {};
|
||||
#define HASH_LEN 20u
|
||||
using InfoHash = Hash<HASH_LEN>;
|
||||
using h256 = Hash<32>;
|
||||
using PkId = h256;
|
||||
|
||||
struct NodeExport {
|
||||
template <size_t N>
|
||||
std::ostream& operator<< (std::ostream& s, const Hash<N>& h)
|
||||
{
|
||||
s.write(h.to_c_str(), N*2);
|
||||
return s;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::istream& operator>> (std::istream& s, Hash<N>& h)
|
||||
{
|
||||
std::array<char, h.size()*2> dat;
|
||||
s.exceptions(std::istream::eofbit | std::istream::failbit);
|
||||
s.read(&(*dat.begin()), dat.size());
|
||||
fromString(dat.data());
|
||||
return s;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
Hash<N>::Hash(const std::string& hex) {
|
||||
if (hex.size() < 2*N)
|
||||
data_.fill(0);
|
||||
else
|
||||
fromString(hex.c_str());
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void
|
||||
Hash<N>::fromString(const char* in) {
|
||||
auto hex2bin = [](char c) -> uint8_t {
|
||||
if (c >= 'a' and c <= 'f') return 10 + c - 'a';
|
||||
else if (c >= 'A' and c <= 'F') return 10 + c - 'A';
|
||||
else if (c >= '0' and c <= '9') return c - '0';
|
||||
else throw std::domain_error("not an hex character");
|
||||
};
|
||||
try {
|
||||
for (size_t i=0; i<N; i++)
|
||||
data_[i] = (hex2bin(in[2*i]) << 4) | hex2bin(in[2*i+1]);
|
||||
} catch (const std::domain_error&) {
|
||||
data_.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
Hash<N>
|
||||
Hash<N>::getRandom()
|
||||
{
|
||||
Hash h;
|
||||
crypto::random_device rdev;
|
||||
std::uniform_int_distribution<uint32_t> rand_int;
|
||||
auto a = reinterpret_cast<uint32_t*>(h.data());
|
||||
auto b = reinterpret_cast<uint32_t*>(h.data() + h.size());
|
||||
std::generate(a, b, std::bind(rand_int, std::ref(rdev)));
|
||||
return h;
|
||||
}
|
||||
|
||||
struct HexMap : public std::array<std::array<char, 2>, 256> {
|
||||
HexMap() {
|
||||
for (size_t i=0; i<size(); i++) {
|
||||
auto& e = (*this)[i];
|
||||
e[0] = hex_digits[(i >> 4) & 0x0F];
|
||||
e[1] = hex_digits[i & 0x0F];
|
||||
}
|
||||
}
|
||||
private:
|
||||
static constexpr const char* hex_digits = "0123456789abcdef";
|
||||
};
|
||||
|
||||
OPENDHT_PUBLIC extern const HexMap hex_map;
|
||||
|
||||
template <size_t N>
|
||||
const char*
|
||||
Hash<N>::to_c_str() const
|
||||
{
|
||||
thread_local std::array<char, N*2+1> buf;
|
||||
for (size_t i=0; i<N; i++) {
|
||||
auto b = buf.data()+i*2;
|
||||
const auto& m = hex_map[data_[i]];
|
||||
*((uint16_t*)b) = *((uint16_t*)&m);
|
||||
}
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::string
|
||||
Hash<N>::toString() const
|
||||
{
|
||||
return std::string(to_c_str(), N*2);
|
||||
}
|
||||
|
||||
const InfoHash zeroes {};
|
||||
|
||||
struct OPENDHT_PUBLIC NodeExport {
|
||||
InfoHash id;
|
||||
sockaddr_storage ss;
|
||||
socklen_t sslen;
|
||||
|
||||
template <typename Packer>
|
||||
void msgpack_pack(Packer& pk) const
|
||||
{
|
||||
pk.pack_map(2);
|
||||
pk.pack(std::string("id"));
|
||||
pk.pack(id);
|
||||
pk.pack(std::string("addr"));
|
||||
pk.pack_bin(sslen);
|
||||
pk.pack_bin_body((char*)&ss, sslen);
|
||||
}
|
||||
|
||||
void msgpack_unpack(msgpack::object o);
|
||||
|
||||
OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const NodeExport& h);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -24,6 +24,10 @@
|
||||
#include <iostream>
|
||||
|
||||
namespace dht {
|
||||
|
||||
/**
|
||||
* Logging-related functions
|
||||
*/
|
||||
namespace log {
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,7 @@ struct Logger {
|
||||
LogMethod ERR = NOLOG;
|
||||
void setFilter(const InfoHash& f) {
|
||||
filter_ = f;
|
||||
filterEnable_ = filter_ != InfoHash{};
|
||||
filterEnable_ = static_cast<bool>(filter_);
|
||||
}
|
||||
inline void log0(const LogMethod& logger, char const* format, va_list args) const {
|
||||
#if OPENDHT_LOG
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace dht {
|
||||
namespace net {
|
||||
|
||||
struct TransPrefix : public std::array<uint8_t, 2> {
|
||||
TransPrefix(const std::string& str) : std::array<uint8_t, 2>({{(uint8_t)str[0], (uint8_t)str[1]}}) {}
|
||||
static const TransPrefix PING;
|
||||
static const TransPrefix FIND_NODE;
|
||||
static const TransPrefix GET_VALUES;
|
||||
static const TransPrefix ANNOUNCE_VALUES;
|
||||
static const TransPrefix REFRESH;
|
||||
static const TransPrefix LISTEN;
|
||||
};
|
||||
|
||||
/* Transaction-ids are 4-bytes long, with the first two bytes identifying
|
||||
* the kind of request, and the remaining two a sequence number in
|
||||
* host order.
|
||||
*/
|
||||
struct TransId final : public std::array<uint8_t, 4> {
|
||||
static const constexpr uint16_t INVALID {0};
|
||||
|
||||
TransId() { std::fill_n(begin(), 4, 0); }
|
||||
TransId(const std::array<char, 4>& o) { std::copy(o.begin(), o.end(), begin()); }
|
||||
TransId(const TransPrefix prefix, uint16_t seqno = 0) {
|
||||
std::copy_n(prefix.begin(), prefix.size(), begin());
|
||||
*reinterpret_cast<uint16_t*>(data()+prefix.size()) = seqno;
|
||||
}
|
||||
|
||||
TransId(const char* q, size_t l) : array<uint8_t, 4>() {
|
||||
if (l > 4) {
|
||||
length = 0;
|
||||
} else {
|
||||
std::copy_n(q, l, begin());
|
||||
length = l;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t getTid() const {
|
||||
return *reinterpret_cast<const uint16_t*>(&(*this)[2]);
|
||||
}
|
||||
|
||||
uint32_t toInt() const {
|
||||
return *reinterpret_cast<const uint32_t*>(&(*this)[0]);
|
||||
}
|
||||
|
||||
bool matches(const TransPrefix prefix, uint16_t* tid = nullptr) const {
|
||||
if (std::equal(begin(), begin()+2, prefix.begin())) {
|
||||
if (tid)
|
||||
*tid = getTid();
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned length {4};
|
||||
};
|
||||
|
||||
} /* namespace net */
|
||||
} /* dht */
|
||||
@@ -27,14 +27,12 @@
|
||||
#include "utils.h"
|
||||
#include "rng.h"
|
||||
#include "rate_limiter.h"
|
||||
#include "net.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <queue>
|
||||
|
||||
namespace dht {
|
||||
@@ -42,6 +40,7 @@ namespace net {
|
||||
|
||||
struct Request;
|
||||
struct Socket;
|
||||
struct TransId;
|
||||
|
||||
#ifndef MSG_CONFIRM
|
||||
#define MSG_CONFIRM 0
|
||||
@@ -81,6 +80,20 @@ private:
|
||||
|
||||
struct ParsedMessage;
|
||||
|
||||
/**
|
||||
* Answer for a request.
|
||||
*/
|
||||
struct RequestAnswer {
|
||||
Blob ntoken {};
|
||||
Value::Id vid {};
|
||||
std::vector<Sp<Value>> values {};
|
||||
std::vector<Sp<FieldValueIndex>> fields {};
|
||||
std::vector<Sp<Node>> nodes4 {};
|
||||
std::vector<Sp<Node>> nodes6 {};
|
||||
RequestAnswer() {}
|
||||
RequestAnswer(ParsedMessage&& msg);
|
||||
};
|
||||
|
||||
/*!
|
||||
* @class NetworkEngine
|
||||
* @brief An abstraction of communication protocol on the network.
|
||||
@@ -101,26 +114,6 @@ struct ParsedMessage;
|
||||
*/
|
||||
class NetworkEngine final
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* @class RequestAnswer
|
||||
* @brief Answer for a request.
|
||||
* @details
|
||||
* Answer for a request to be (de)serialized. Used for reponding to a node
|
||||
* and looking up the response from a node.
|
||||
*/
|
||||
struct RequestAnswer {
|
||||
Blob ntoken {};
|
||||
Value::Id vid {};
|
||||
std::vector<Sp<Value>> values {};
|
||||
std::vector<Sp<FieldValueIndex>> fields {};
|
||||
std::vector<Sp<Node>> nodes4 {};
|
||||
std::vector<Sp<Node>> nodes6 {};
|
||||
RequestAnswer() {}
|
||||
RequestAnswer(ParsedMessage&& msg);
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
/**
|
||||
* Called when we receive an error message.
|
||||
@@ -137,7 +130,7 @@ private:
|
||||
/**
|
||||
* Called when an addres is reported from a requested node.
|
||||
*
|
||||
* @param h: id
|
||||
* @param h: id
|
||||
* @param saddr_len (type: socklen_t) lenght of the sockaddr struct.
|
||||
*/
|
||||
std::function<void(const InfoHash&, const SockAddr&)> onReportedAddr;
|
||||
@@ -176,7 +169,7 @@ private:
|
||||
std::function<RequestAnswer(Sp<Node>,
|
||||
const InfoHash&,
|
||||
const Blob&,
|
||||
uint32_t,
|
||||
Tid,
|
||||
const Query&)> onListen {};
|
||||
/**
|
||||
* Called on announce request.
|
||||
@@ -206,12 +199,11 @@ private:
|
||||
const Value::Id&)> onRefresh {};
|
||||
|
||||
public:
|
||||
using SocketCb = std::function<void(const Sp<Node>&, RequestAnswer&&)>;
|
||||
using RequestCb = std::function<void(const Request&, RequestAnswer&&)>;
|
||||
using RequestExpiredCb = std::function<void(const Request&, bool)>;
|
||||
|
||||
NetworkEngine(Logger& log, Scheduler& scheduler);
|
||||
NetworkEngine(InfoHash& myid, NetId net, int s, int s6, Logger& log, Scheduler& scheduler,
|
||||
NetworkEngine(Logger& log, Scheduler& scheduler, const int& s = -1, const int& s6 = -1);
|
||||
NetworkEngine(InfoHash& myid, NetId net, const int& s, const int& s6, Logger& log, Scheduler& scheduler,
|
||||
decltype(NetworkEngine::onError) onError,
|
||||
decltype(NetworkEngine::onNewNode) onNewNode,
|
||||
decltype(NetworkEngine::onReportedAddr) onReportedAddr,
|
||||
@@ -239,19 +231,13 @@ public:
|
||||
* @param nodes6 The ipv6 closest nodes.
|
||||
* @param values The values to send.
|
||||
*/
|
||||
void tellListener(Sp<Node> n, uint32_t socket_id, const InfoHash& hash, want_t want, const Blob& ntoken,
|
||||
void tellListener(Sp<Node> n, Tid socket_id, const InfoHash& hash, want_t want, const Blob& ntoken,
|
||||
std::vector<Sp<Node>>&& nodes, std::vector<Sp<Node>>&& nodes6,
|
||||
std::vector<Sp<Value>>&& values, const Query& q);
|
||||
|
||||
bool isRunning(sa_family_t af) const;
|
||||
inline want_t want () const { return dht_socket >= 0 && dht_socket6 >= 0 ? (WANT4 | WANT6) : -1; }
|
||||
|
||||
/**
|
||||
* Cancel a request. Setting req->cancelled = true is not enough in the case
|
||||
* a request is "persistent".
|
||||
*/
|
||||
void cancelRequest(Sp<Request>& req);
|
||||
|
||||
void connectivityChanged(sa_family_t);
|
||||
|
||||
/**************
|
||||
@@ -392,23 +378,6 @@ public:
|
||||
const Blob& token,
|
||||
RequestCb&& on_done,
|
||||
RequestExpiredCb&& on_expired);
|
||||
/**
|
||||
* Opens a socket on which a node will be able allowed to write for further
|
||||
* additionnal updates following the response to a previous request.
|
||||
*
|
||||
* @param node The node which will be allowed to write on this socket.
|
||||
* @param cb The callback to execute once updates arrive on the socket.
|
||||
*
|
||||
* @return the socket.
|
||||
*/
|
||||
Sp<Socket> openSocket(const Sp<Node>& node, TransPrefix tp, SocketCb&& cb);
|
||||
|
||||
/**
|
||||
* Closes a socket so that no further data will be red on that socket.
|
||||
*
|
||||
* @param socket The socket to close.
|
||||
*/
|
||||
void closeSocket(Sp<Socket> socket);
|
||||
|
||||
/**
|
||||
* Parses a message and calls appropriate callbacks.
|
||||
@@ -447,7 +416,7 @@ private:
|
||||
/***************
|
||||
* Constants *
|
||||
***************/
|
||||
static constexpr long unsigned MAX_REQUESTS_PER_SEC {1600};
|
||||
static constexpr size_t MAX_REQUESTS_PER_SEC {1600};
|
||||
/* the length of a node info buffer in ipv4 format */
|
||||
static const constexpr size_t NODE4_INFO_BUF_LEN {HASH_LEN + sizeof(in_addr) + sizeof(in_port_t)};
|
||||
/* the length of a node info buffer in ipv6 format */
|
||||
@@ -467,7 +436,6 @@ private:
|
||||
static constexpr size_t MAX_PACKET_VALUE_SIZE {600};
|
||||
|
||||
static const std::string my_v;
|
||||
static std::mt19937 rd_device;
|
||||
|
||||
void process(std::unique_ptr<ParsedMessage>&&, const SockAddr& from);
|
||||
|
||||
@@ -482,17 +450,7 @@ private:
|
||||
* Sends a request to a node. Request::MAX_ATTEMPT_COUNT attempts will
|
||||
* be made before the request expires.
|
||||
*/
|
||||
void sendRequest(Sp<Request>& request);
|
||||
|
||||
/**
|
||||
* Generates a new request id, skipping the invalid id.
|
||||
*
|
||||
* @return the new id.
|
||||
*/
|
||||
uint16_t getNewTid() {
|
||||
++transaction_id;
|
||||
return transaction_id == TransId::INVALID ? ++transaction_id : transaction_id;
|
||||
}
|
||||
void sendRequest(const Sp<Request>& request);
|
||||
|
||||
struct MessageStats {
|
||||
unsigned ping {0};
|
||||
@@ -507,18 +465,18 @@ private:
|
||||
// basic wrapper for socket sendto function
|
||||
int send(const char *buf, size_t len, int flags, const SockAddr& addr);
|
||||
|
||||
void sendValueParts(TransId tid, const std::vector<Blob>& svals, const SockAddr& addr);
|
||||
void sendValueParts(const TransId& tid, const std::vector<Blob>& svals, const SockAddr& addr);
|
||||
std::vector<Blob> packValueHeader(msgpack::sbuffer&, const std::vector<Sp<Value>>&);
|
||||
void maintainRxBuffer(const TransId& tid);
|
||||
void maintainRxBuffer(Tid tid);
|
||||
|
||||
/*************
|
||||
* Answers *
|
||||
*************/
|
||||
/* answer to a ping request */
|
||||
void sendPong(const SockAddr& addr, TransId tid);
|
||||
void sendPong(const SockAddr& addr, Tid tid);
|
||||
/* answer to findnodes/getvalues request */
|
||||
void sendNodesValues(const SockAddr& addr,
|
||||
TransId tid,
|
||||
Tid tid,
|
||||
const Blob& nodes,
|
||||
const Blob& nodes6,
|
||||
const std::vector<Sp<Value>>& st,
|
||||
@@ -532,12 +490,12 @@ private:
|
||||
std::vector<Sp<Node>>& nodes,
|
||||
std::vector<Sp<Node>>& nodes6);
|
||||
/* answer to a listen request */
|
||||
void sendListenConfirmation(const SockAddr& addr, TransId tid);
|
||||
void sendListenConfirmation(const SockAddr& addr, Tid tid);
|
||||
/* answer to put request */
|
||||
void sendValueAnnounced(const SockAddr& addr, TransId, Value::Id);
|
||||
void sendValueAnnounced(const SockAddr& addr, Tid, Value::Id);
|
||||
/* answer in case of error */
|
||||
void sendError(const SockAddr& addr,
|
||||
TransId tid,
|
||||
Tid tid,
|
||||
uint16_t code,
|
||||
const std::string& message,
|
||||
bool include_id=false);
|
||||
@@ -547,51 +505,22 @@ private:
|
||||
/* DHT info */
|
||||
const InfoHash& myid;
|
||||
const NetId network {0};
|
||||
const int dht_socket {-1};
|
||||
const int dht_socket6 {-1};
|
||||
const int& dht_socket;
|
||||
const int& dht_socket6;
|
||||
const Logger& DHT_LOG;
|
||||
|
||||
NodeCache cache {};
|
||||
|
||||
/**
|
||||
* A comparator to classify IP addresses, only considering the
|
||||
* first 64 bits in IPv6.
|
||||
*/
|
||||
struct cmpSockAddr {
|
||||
bool operator()(const SockAddr& a, const SockAddr& b) const {
|
||||
if (a.second != b.second)
|
||||
return a.second < b.second;
|
||||
socklen_t start, len;
|
||||
switch(a.getFamily()) {
|
||||
case AF_INET:
|
||||
start = offsetof(sockaddr_in, sin_addr);
|
||||
len = sizeof(in_addr);
|
||||
break;
|
||||
case AF_INET6:
|
||||
start = offsetof(sockaddr_in6, sin6_addr);
|
||||
// don't consider more than 64 bits (IPv6)
|
||||
len = 8;
|
||||
break;
|
||||
default:
|
||||
start = 0;
|
||||
len = a.second;
|
||||
break;
|
||||
}
|
||||
|
||||
return std::memcmp((uint8_t*)&a.first+start, (uint8_t*)&b.first+start, len) < 0;
|
||||
}
|
||||
};
|
||||
// global limiting should be triggered by at least 8 different IPs
|
||||
using IpLimiter = RateLimiter<MAX_REQUESTS_PER_SEC/8>;
|
||||
using IpLimiterMap = std::map<SockAddr, IpLimiter, SockAddr::ipCmp>;
|
||||
IpLimiterMap address_rate_limiter {};
|
||||
RateLimiter<MAX_REQUESTS_PER_SEC> rate_limiter {};
|
||||
size_t limiter_maintenance {0};
|
||||
|
||||
// requests handling
|
||||
uint16_t transaction_id {1};
|
||||
std::map<TransId, Sp<Request>> requests {};
|
||||
std::map<TransId, PartialMessage> partial_messages;
|
||||
std::map<TransId, Sp<Socket>> opened_sockets {};
|
||||
std::map<Tid, Sp<Request>> requests {};
|
||||
std::map<Tid, PartialMessage> partial_messages;
|
||||
|
||||
MessageStats in_stats {}, out_stats {};
|
||||
std::set<SockAddr> blacklist {};
|
||||
|
||||
+79
-19
@@ -24,33 +24,46 @@
|
||||
#include "sockaddr.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
namespace dht {
|
||||
|
||||
struct Node;
|
||||
namespace net {
|
||||
struct Request;
|
||||
struct Socket;
|
||||
struct RequestAnswer;
|
||||
} /* namespace net */
|
||||
|
||||
using Tid = uint32_t;
|
||||
using SocketCb = std::function<void(const Sp<Node>&, net::RequestAnswer&&)>;
|
||||
struct Socket {
|
||||
Socket() {}
|
||||
Socket(SocketCb&& on_receive) :
|
||||
on_receive(std::move(on_receive)) {}
|
||||
SocketCb on_receive {};
|
||||
};
|
||||
|
||||
struct Node {
|
||||
InfoHash id;
|
||||
SockAddr addr;
|
||||
|
||||
time_point time {time_point::min()}; /* last time eared about */
|
||||
time_point reply_time {time_point::min()}; /* time of last correct reply received */
|
||||
const InfoHash id;
|
||||
|
||||
Node(const InfoHash& id, const SockAddr& addr, bool client=false);
|
||||
Node(const InfoHash& id, const sockaddr* sa, socklen_t salen)
|
||||
: id(id), addr(sa, salen) {}
|
||||
Node(const InfoHash& id, const SockAddr& addr) : id(id), addr(addr) {}
|
||||
: Node(id, SockAddr(sa, salen)) {}
|
||||
|
||||
InfoHash getId() const {
|
||||
return id;
|
||||
}
|
||||
std::pair<const sockaddr*, socklen_t> getAddr() const {
|
||||
return {(const sockaddr*)&addr.first, addr.second};
|
||||
}
|
||||
const SockAddr& getAddr() const { return addr; }
|
||||
std::string getAddrStr() const {
|
||||
return addr.toString();
|
||||
}
|
||||
bool isClient() const { return is_client; }
|
||||
bool isIncoming() { return time > reply_time; }
|
||||
|
||||
const time_point& getTime() const { return time; }
|
||||
const time_point& getReplyTime() const { return reply_time; }
|
||||
void setTime(const time_point& t) { time = t; }
|
||||
|
||||
/**
|
||||
* Makes notice about an additionnal authentication error with this node. Up
|
||||
@@ -68,21 +81,66 @@ struct Node {
|
||||
bool isPendingMessage() const;
|
||||
size_t getPendingMessageCount() const;
|
||||
|
||||
NodeExport exportNode() const { return NodeExport {id, addr.first, addr.second}; }
|
||||
bool isOld(const time_point& now) const {
|
||||
return time + NODE_EXPIRE_TIME < now;
|
||||
}
|
||||
bool isRemovable(const time_point& now) const {
|
||||
return isExpired() and isOld(now);
|
||||
}
|
||||
|
||||
NodeExport exportNode() const {
|
||||
NodeExport ne;
|
||||
ne.id = id;
|
||||
ne.sslen = addr.getLength();
|
||||
std::memcpy(&ne.ss, addr.get(), ne.sslen);
|
||||
return ne;
|
||||
}
|
||||
sa_family_t getFamily() const { return addr.getFamily(); }
|
||||
|
||||
void update(const SockAddr&);
|
||||
|
||||
void requested(std::shared_ptr<net::Request>& req);
|
||||
void received(time_point now, std::shared_ptr<net::Request> req);
|
||||
void requested(const Sp<net::Request>& req);
|
||||
void received(time_point now, const Sp<net::Request>& req);
|
||||
Sp<net::Request> getRequest(Tid tid);
|
||||
void cancelRequest(const Sp<net::Request>& req);
|
||||
|
||||
void setExpired();
|
||||
|
||||
/**
|
||||
* Opens a socket on which a node will be able allowed to write for further
|
||||
* additionnal updates following the response to a previous request.
|
||||
*
|
||||
* @param node The node which will be allowed to write on this socket.
|
||||
* @param cb The callback to execute once updates arrive on the socket.
|
||||
*
|
||||
* @return the socket.
|
||||
*/
|
||||
Tid openSocket(SocketCb&& cb);
|
||||
|
||||
Sp<Socket> getSocket(Tid id);
|
||||
|
||||
/**
|
||||
* Closes a socket so that no further data will be red on that socket.
|
||||
*
|
||||
* @param socket The socket to close.
|
||||
*/
|
||||
void closeSocket(Tid id);
|
||||
|
||||
/**
|
||||
* Resets the state of the node so it's not expired anymore.
|
||||
*/
|
||||
void reset() { expired_ = false; reply_time = time_point::min(); }
|
||||
|
||||
/**
|
||||
* Generates a new request id, skipping the invalid id.
|
||||
*
|
||||
* @return the new id.
|
||||
*/
|
||||
Tid getNewTid() {
|
||||
++transaction_id;
|
||||
return transaction_id ? ++transaction_id : transaction_id;
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
OPENDHT_PUBLIC friend std::ostream& operator<< (std::ostream& s, const Node& h);
|
||||
@@ -99,15 +157,17 @@ private:
|
||||
/* Number of times we accept authentication errors from this node. */
|
||||
static const constexpr unsigned MAX_AUTH_ERRORS {3};
|
||||
|
||||
std::list<std::weak_ptr<net::Request>> requests_ {};
|
||||
SockAddr addr;
|
||||
bool is_client {false};
|
||||
time_point time {time_point::min()}; /* last time eared about */
|
||||
time_point reply_time {time_point::min()}; /* time of last correct reply received */
|
||||
unsigned auth_errors {0};
|
||||
bool expired_ {false};
|
||||
Tid transaction_id;
|
||||
using TransactionDist = std::uniform_int_distribution<decltype(transaction_id)>;
|
||||
|
||||
void clearPendingQueue() {
|
||||
requests_.remove_if([](std::weak_ptr<net::Request>& w) {
|
||||
return w.expired();
|
||||
});
|
||||
}
|
||||
std::map<Tid, Sp<net::Request>> requests_ {};
|
||||
std::map<Tid, Sp<Socket>> sockets_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -26,9 +26,9 @@
|
||||
namespace dht {
|
||||
|
||||
struct NodeCache {
|
||||
std::shared_ptr<Node> getNode(const InfoHash& id, sa_family_t family);
|
||||
std::shared_ptr<Node> getNode(const InfoHash& id, const SockAddr&, time_point now, bool confirmed);
|
||||
std::vector<std::shared_ptr<Node>> getCachedNodes(const InfoHash& id, sa_family_t sa_f, size_t count);
|
||||
Sp<Node> getNode(const InfoHash& id, sa_family_t family);
|
||||
Sp<Node> getNode(const InfoHash& id, const SockAddr&, time_point now, bool confirmed, bool client=false);
|
||||
std::vector<Sp<Node>> getCachedNodes(const InfoHash& id, sa_family_t sa_f, size_t count) const;
|
||||
|
||||
/**
|
||||
* Reset the connectivity state of every node,
|
||||
@@ -37,14 +37,19 @@ struct NodeCache {
|
||||
*/
|
||||
void clearBadNodes(sa_family_t family = 0);
|
||||
|
||||
~NodeCache();
|
||||
|
||||
private:
|
||||
class NodeMap : public std::map<InfoHash, std::weak_ptr<Node>> {
|
||||
public:
|
||||
std::shared_ptr<Node> getNode(const InfoHash& id);
|
||||
std::shared_ptr<Node> getNode(const InfoHash& id, const SockAddr&, time_point now, bool confirmed);
|
||||
Sp<Node> getNode(const InfoHash& id);
|
||||
Sp<Node> getNode(const InfoHash& id, const SockAddr&, time_point now, bool confirmed, bool client);
|
||||
void clearBadNodes();
|
||||
void setExpired();
|
||||
};
|
||||
|
||||
const NodeMap& cache(sa_family_t af) const { return af == AF_INET ? cache_4 : cache_6; }
|
||||
NodeMap& cache(sa_family_t af) { return af == AF_INET ? cache_4 : cache_6; }
|
||||
NodeMap cache_4;
|
||||
NodeMap cache_6;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Savoir-faire Linux Inc.
|
||||
* Copyright (C) 2017 Savoir-faire Linux Inc.
|
||||
* Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -23,13 +23,13 @@
|
||||
|
||||
namespace dht {
|
||||
|
||||
template<size_t Quota, unsigned long Perdiod=1>
|
||||
template<size_t Quota, unsigned long Period=1>
|
||||
class RateLimiter {
|
||||
public:
|
||||
/** Clear outdated records and return current quota usage */
|
||||
/** Clear outdated records and return current quota usage */
|
||||
size_t maintain(const time_point& now) {
|
||||
using namespace std::chrono;
|
||||
while (not records.empty() and duration_cast<seconds>(now - records.front()) >= seconds(Perdiod))
|
||||
auto limit = now - std::chrono::seconds(Period);
|
||||
while (not records.empty() and records.front() < limit)
|
||||
records.pop();
|
||||
return records.size();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
namespace dht {
|
||||
|
||||
static constexpr unsigned TARGET_NODES {8};
|
||||
namespace net {
|
||||
class NetworkEngine;
|
||||
}
|
||||
|
||||
struct Bucket {
|
||||
Bucket() : cached() {}
|
||||
@@ -37,12 +40,17 @@ struct Bucket {
|
||||
|
||||
/** Return a random node in a bucket. */
|
||||
Sp<Node> randomNode();
|
||||
|
||||
void sendCachedPing(net::NetworkEngine& ne);
|
||||
};
|
||||
|
||||
class RoutingTable : public std::list<Bucket> {
|
||||
public:
|
||||
using std::list<Bucket>::list;
|
||||
|
||||
time_point grow_time {time_point::min()};
|
||||
bool is_client {false};
|
||||
|
||||
InfoHash middle(const RoutingTable::const_iterator&) const;
|
||||
|
||||
std::vector<Sp<Node>> findClosestNodes(const InfoHash id, time_point now, size_t count = TARGET_NODES) const;
|
||||
@@ -65,6 +73,14 @@ public:
|
||||
return empty() || (size() == 1 && front().nodes.empty());
|
||||
}
|
||||
|
||||
void connectivityChanged(const time_point& now) {
|
||||
grow_time = now;
|
||||
for (auto& b : *this)
|
||||
b.time = time_point::min();
|
||||
}
|
||||
|
||||
bool onNewNode(const Sp<Node>& node, int comfirm, const time_point& now, const InfoHash& myid, net::NetworkEngine& ne);
|
||||
|
||||
/**
|
||||
* Return a random id in the bucket's range.
|
||||
*/
|
||||
|
||||
+216
-16
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -29,13 +30,21 @@
|
||||
|
||||
namespace dht {
|
||||
|
||||
class OPENDHT_PUBLIC SecureDht : public Dht {
|
||||
class OPENDHT_PUBLIC SecureDht final : public DhtInterface {
|
||||
public:
|
||||
|
||||
typedef std::function<void(bool)> SignatureCheckCallback;
|
||||
|
||||
using Config = SecureDhtConfig;
|
||||
|
||||
static dht::Config& getConfig(SecureDht::Config& conf)
|
||||
{
|
||||
auto& c = conf.node_config;
|
||||
if (not c.node_id and conf.id.second)
|
||||
c.node_id = InfoHash::get("node:"+conf.id.second->getId().toString());
|
||||
return c;
|
||||
}
|
||||
|
||||
SecureDht() {}
|
||||
|
||||
/**
|
||||
@@ -44,7 +53,7 @@ public:
|
||||
* id: the identity to use for the crypto layer and to compute
|
||||
* our own hash on the Dht.
|
||||
*/
|
||||
SecureDht(int s, int s6, Config config);
|
||||
SecureDht(std::unique_ptr<DhtInterface> dht, Config config);
|
||||
|
||||
virtual ~SecureDht();
|
||||
|
||||
@@ -62,14 +71,17 @@ public:
|
||||
return secureType(std::move(tmp_type));
|
||||
}
|
||||
|
||||
virtual void registerType(const ValueType& type) override {
|
||||
Dht::registerType(secureType(type));
|
||||
void registerType(const ValueType& type) {
|
||||
if (dht_)
|
||||
dht_->registerType(secureType(type));
|
||||
}
|
||||
virtual void registerType(ValueType&& type) {
|
||||
Dht::registerType(secureType(std::forward<ValueType>(type)));
|
||||
void registerType(ValueType&& type) {
|
||||
if (dht_)
|
||||
dht_->registerType(secureType(std::forward<ValueType>(type)));
|
||||
}
|
||||
virtual void registerInsecureType(const ValueType& type) {
|
||||
Dht::registerType(type);
|
||||
void registerInsecureType(const ValueType& type) {
|
||||
if (dht_)
|
||||
dht_->registerType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,18 +89,18 @@ public:
|
||||
* If the signature can't be checked, or if the data can't be decrypted, it is not returned.
|
||||
* Public, non-signed & non-encrypted data is retransmitted as-is.
|
||||
*/
|
||||
virtual void get(const InfoHash& id, GetCallback cb, DoneCallback donecb={}, Value::Filter&& = {}, Where&& w = {}) override;
|
||||
virtual void get(const InfoHash& id, GetCallback cb, DoneCallbackSimple donecb={}, Value::Filter&& f = {}, Where&& w = {}) override {
|
||||
void get(const InfoHash& id, GetCallback cb, DoneCallback donecb={}, Value::Filter&& = {}, Where&& w = {});
|
||||
void get(const InfoHash& id, GetCallback cb, DoneCallbackSimple donecb={}, Value::Filter&& f = {}, Where&& w = {}) {
|
||||
get(id, cb, bindDoneCb(donecb), std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
virtual void get(const InfoHash& key, GetCallbackSimple cb, DoneCallback donecb={}, Value::Filter&& f={}, Where&& w = {}) override {
|
||||
void get(const InfoHash& key, GetCallbackSimple cb, DoneCallback donecb={}, Value::Filter&& f={}, Where&& w = {}) {
|
||||
get(key, bindGetCb(cb), donecb, std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
virtual void get(const InfoHash& key, GetCallbackSimple cb, DoneCallbackSimple donecb, Value::Filter&& f={}, Where&& w = {}) override {
|
||||
void get(const InfoHash& key, GetCallbackSimple cb, DoneCallbackSimple donecb, Value::Filter&& f={}, Where&& w = {}) {
|
||||
get(key, bindGetCb(cb), bindDoneCb(donecb), std::forward<Value::Filter>(f), std::forward<Where>(w));
|
||||
}
|
||||
|
||||
virtual size_t listen(const InfoHash& id, GetCallback cb, Value::Filter&& = {}, Where&& w = {}) override;
|
||||
size_t listen(const InfoHash& id, GetCallback cb, Value::Filter = {}, Where w = {});
|
||||
|
||||
/**
|
||||
* Will take ownership of the value, sign it using our private key and put it in the DHT.
|
||||
@@ -135,7 +147,193 @@ public:
|
||||
localQueryMethod_ = std::move(query_method);
|
||||
}
|
||||
|
||||
/**
|
||||
* SecureDht to Dht proxy
|
||||
*/
|
||||
void shutdown(ShutdownCallback cb) {
|
||||
dht_->shutdown(cb);
|
||||
}
|
||||
void dumpTables() const {
|
||||
dht_->dumpTables();
|
||||
}
|
||||
inline const InfoHash& getNodeId() const { return dht_->getNodeId(); }
|
||||
std::pair<size_t, size_t> getStoreSize() const {
|
||||
return dht_->getStoreSize();
|
||||
}
|
||||
std::string getStorageLog() const {
|
||||
return dht_->getStorageLog();
|
||||
}
|
||||
std::string getStorageLog(const InfoHash& h) const {
|
||||
return dht_->getStorageLog(h);
|
||||
}
|
||||
void setStorageLimit(size_t limit = DEFAULT_STORAGE_LIMIT) {
|
||||
dht_->setStorageLimit(limit);
|
||||
}
|
||||
std::vector<NodeExport> exportNodes() {
|
||||
return dht_->exportNodes();
|
||||
}
|
||||
std::vector<ValuesExport> exportValues() const {
|
||||
return dht_->exportValues();
|
||||
}
|
||||
void importValues(const std::vector<ValuesExport>& v) {
|
||||
dht_->importValues(v);
|
||||
}
|
||||
NodeStats getNodesStats(sa_family_t af) const {
|
||||
return dht_->getNodesStats(af);
|
||||
}
|
||||
std::vector<unsigned> getNodeMessageStats(bool in = false) {
|
||||
return dht_->getNodeMessageStats(in);
|
||||
}
|
||||
std::string getRoutingTablesLog(sa_family_t af) const {
|
||||
return dht_->getRoutingTablesLog(af);
|
||||
}
|
||||
std::string getSearchesLog(sa_family_t af) const {
|
||||
return dht_->getSearchesLog(af);
|
||||
}
|
||||
std::string getSearchLog(const InfoHash& h, sa_family_t af = AF_UNSPEC) const {
|
||||
return dht_->getSearchLog(h, af);
|
||||
}
|
||||
std::vector<SockAddr> getPublicAddress(sa_family_t family = 0) {
|
||||
return dht_->getPublicAddress(family);
|
||||
}
|
||||
time_point periodic(const uint8_t *buf, size_t buflen, const SockAddr& sa) {
|
||||
return dht_->periodic(buf, buflen, sa);
|
||||
}
|
||||
time_point periodic(const uint8_t *buf, size_t buflen, const sockaddr* from, socklen_t fromlen) {
|
||||
return dht_->periodic(buf, buflen, from, fromlen);
|
||||
}
|
||||
NodeStatus getStatus(sa_family_t af) const {
|
||||
return dht_->getStatus(af);
|
||||
}
|
||||
NodeStatus getStatus() const {
|
||||
return dht_->getStatus();
|
||||
}
|
||||
bool isRunning(sa_family_t af = 0) const {
|
||||
return dht_->isRunning(af);
|
||||
}
|
||||
const ValueType& getType(ValueType::Id type_id) const {
|
||||
return dht_->getType(type_id);
|
||||
}
|
||||
void insertNode(const InfoHash& id, const SockAddr& sa) {
|
||||
dht_->insertNode(id, sa);
|
||||
}
|
||||
void insertNode(const InfoHash& id, const sockaddr* sa, socklen_t salen) {
|
||||
dht_->insertNode(id, sa, salen);
|
||||
}
|
||||
void insertNode(const NodeExport& n) {
|
||||
dht_->insertNode(n);
|
||||
}
|
||||
void pingNode(const sockaddr* sa, socklen_t salen, DoneCallbackSimple&& cb={}) {
|
||||
dht_->pingNode(sa, salen, std::move(cb));
|
||||
}
|
||||
void query(const InfoHash& key, QueryCallback cb, DoneCallback done_cb = {}, Query&& q = {}) {
|
||||
dht_->query(key, cb, done_cb, std::move(q));
|
||||
}
|
||||
void query(const InfoHash& key, QueryCallback cb, DoneCallbackSimple done_cb = {}, Query&& q = {}) {
|
||||
dht_->query(key, cb, done_cb, std::move(q));
|
||||
}
|
||||
std::vector<Sp<Value>> getLocal(const InfoHash& key, Value::Filter f = Value::AllFilter()) const {
|
||||
return dht_->getLocal(key, f);
|
||||
}
|
||||
Sp<Value> getLocalById(const InfoHash& key, Value::Id vid) const {
|
||||
return dht_->getLocalById(key, vid);
|
||||
}
|
||||
void put(const InfoHash& key,
|
||||
Sp<Value> v,
|
||||
DoneCallback cb=nullptr,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
dht_->put(key, v, cb, created, permanent);
|
||||
}
|
||||
void put(const InfoHash& key,
|
||||
const Sp<Value>& v,
|
||||
DoneCallbackSimple cb,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
dht_->put(key, v, cb, created, permanent);
|
||||
}
|
||||
|
||||
void put(const InfoHash& key,
|
||||
Value&& v,
|
||||
DoneCallback cb=nullptr,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
dht_->put(key, std::move(v), cb, created, permanent);
|
||||
}
|
||||
void put(const InfoHash& key,
|
||||
Value&& v,
|
||||
DoneCallbackSimple cb,
|
||||
time_point created=time_point::max(),
|
||||
bool permanent = false)
|
||||
{
|
||||
dht_->put(key, std::move(v), cb, created, permanent);
|
||||
}
|
||||
std::vector<Sp<Value>> getPut(const InfoHash& h) {
|
||||
return dht_->getPut(h);
|
||||
}
|
||||
Sp<Value> getPut(const InfoHash& h, const Value::Id& vid) {
|
||||
return dht_->getPut(h, vid);
|
||||
}
|
||||
bool cancelPut(const InfoHash& h, const Value::Id& vid) {
|
||||
return dht_->cancelPut(h, vid);
|
||||
}
|
||||
size_t listen(const InfoHash& key, GetCallbackSimple cb, Value::Filter f={}, Where w = {}) {
|
||||
return dht_->listen(key, cb, f, w);
|
||||
}
|
||||
bool cancelListen(const InfoHash& h, size_t token) {
|
||||
return dht_->cancelListen(h, token);
|
||||
}
|
||||
void connectivityChanged(sa_family_t af) {
|
||||
dht_->connectivityChanged(af);
|
||||
}
|
||||
void connectivityChanged() {
|
||||
dht_->connectivityChanged();
|
||||
}
|
||||
|
||||
void setPushNotificationToken(const std::string& token = "") {
|
||||
dht_->setPushNotificationToken(token);
|
||||
}
|
||||
|
||||
void forwardAllMessages(bool forward) {
|
||||
forward_all_ = forward;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call linked callback with push_notification
|
||||
* @param notification to process
|
||||
*/
|
||||
void pushNotificationReceived(const std::map<std::string, std::string>& notification) {
|
||||
dht_->pushNotificationReceived(notification);
|
||||
}
|
||||
/**
|
||||
* Refresh a listen via a token
|
||||
* @param token
|
||||
*/
|
||||
void resubscribe(const unsigned token) {
|
||||
dht_->resubscribe(token);
|
||||
}
|
||||
|
||||
void setLoggers(LogMethod error = NOLOG, LogMethod warn = NOLOG, LogMethod debug = NOLOG)
|
||||
{
|
||||
DHT_LOG.DEBUG = debug;
|
||||
DHT_LOG.WARN = warn;
|
||||
DHT_LOG.ERR = error;
|
||||
dht_->setLoggers(std::forward<LogMethod>(error), std::forward<LogMethod>(warn), std::forward<LogMethod>(debug));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only print logs related to the given InfoHash (if given), or disable filter (if zeroes).
|
||||
*/
|
||||
void setLogFilter(const InfoHash& f) {
|
||||
DHT_LOG.setFilter(f);
|
||||
dht_->setLogFilter(f);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<DhtInterface> dht_;
|
||||
// prevent copy
|
||||
SecureDht(const SecureDht&) = delete;
|
||||
SecureDht& operator=(const SecureDht&) = delete;
|
||||
@@ -153,12 +351,14 @@ private:
|
||||
std::map<InfoHash, Sp<const crypto::PublicKey>> nodesPubKeys_ {};
|
||||
|
||||
std::uniform_int_distribution<Value::Id> rand_id {};
|
||||
|
||||
std::atomic_bool forward_all_ {false};
|
||||
};
|
||||
|
||||
const ValueType CERTIFICATE_TYPE = {
|
||||
8, "Certificate", std::chrono::hours(24 * 7),
|
||||
// A certificate can only be stored at its public key ID.
|
||||
[](InfoHash id, Sp<Value>& v, InfoHash, const sockaddr*, socklen_t) {
|
||||
[](InfoHash id, Sp<Value>& v, const InfoHash&, const SockAddr&) {
|
||||
try {
|
||||
crypto::Certificate crt(v->data);
|
||||
// TODO check certificate signature
|
||||
@@ -166,7 +366,7 @@ const ValueType CERTIFICATE_TYPE = {
|
||||
} catch (const std::exception& e) {}
|
||||
return false;
|
||||
},
|
||||
[](InfoHash, const Sp<Value>& o, Sp<Value>& n, InfoHash, const sockaddr*, socklen_t) {
|
||||
[](InfoHash, const Sp<Value>& o, Sp<Value>& n, const InfoHash&, const SockAddr&) {
|
||||
try {
|
||||
return crypto::Certificate(o->data).getPublicKey().getId() == crypto::Certificate(n->data).getPublicKey().getId();
|
||||
} catch (const std::exception& e) {}
|
||||
|
||||
+148
-38
@@ -28,62 +28,128 @@ typedef uint16_t in_port_t;
|
||||
#endif
|
||||
#else
|
||||
#include <iso646.h>
|
||||
#include <stdint.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2def.h>
|
||||
#include <ws2tcpip.h>
|
||||
typedef uint16_t sa_family_t;
|
||||
typedef uint16_t in_port_t;
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
|
||||
namespace dht {
|
||||
|
||||
OPENDHT_PUBLIC std::string print_addr(const sockaddr* sa, socklen_t slen);
|
||||
OPENDHT_PUBLIC std::string print_addr(const sockaddr_storage& ss, socklen_t sslen);
|
||||
|
||||
struct OPENDHT_PUBLIC SockAddr : public std::pair<sockaddr_storage, socklen_t> {
|
||||
/**
|
||||
* A Socket Address (sockaddr*), with abstraction for IPv4, IPv6 address families.
|
||||
*/
|
||||
class OPENDHT_PUBLIC SockAddr {
|
||||
public:
|
||||
SockAddr() : pair<sockaddr_storage, socklen_t>::pair({},0) {}
|
||||
SockAddr(const SockAddr& o) : pair<sockaddr_storage, socklen_t>::pair({},o.second) {
|
||||
std::copy_n((uint8_t*)&o.first, o.second, (uint8_t*)&first);
|
||||
SockAddr() {}
|
||||
SockAddr(const SockAddr& o) {
|
||||
set(o.get(), o.getLength());
|
||||
}
|
||||
SockAddr(const sockaddr* sa, socklen_t len) : pair<sockaddr_storage, socklen_t>::pair({},len) {
|
||||
if (len > sizeof(sockaddr_storage))
|
||||
SockAddr(SockAddr&& o) : len(o.len), addr(std::move(o.addr)) {
|
||||
o.len = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build from existing address.
|
||||
*/
|
||||
SockAddr(const sockaddr* sa, socklen_t length) {
|
||||
if (length > sizeof(sockaddr_storage))
|
||||
throw std::runtime_error("Socket address length is too large");
|
||||
std::copy_n((uint8_t*)sa, len, (uint8_t*)&first);
|
||||
set(sa, length);
|
||||
}
|
||||
SockAddr(const sockaddr* sa) {
|
||||
socklen_t len = 0;
|
||||
if (sa) {
|
||||
if (sa->sa_family == AF_INET)
|
||||
len = sizeof(sockaddr_in);
|
||||
else if(sa->sa_family == AF_INET6)
|
||||
len = sizeof(sockaddr_in6);
|
||||
else
|
||||
throw std::runtime_error("Unknown address family");
|
||||
}
|
||||
set(sa, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build from an existing sockaddr_storage structure.
|
||||
*/
|
||||
SockAddr(const sockaddr_storage& ss, socklen_t len) : SockAddr((const sockaddr*)&ss, len) {}
|
||||
|
||||
static std::vector<SockAddr> resolve(const std::string& host, const std::string& service = {});
|
||||
|
||||
bool operator<(const SockAddr& o) const {
|
||||
if (second != o.second)
|
||||
return second < o.second;
|
||||
return std::memcmp((uint8_t*)&first, (uint8_t*)&o.first, second) < 0;
|
||||
if (len != o.len)
|
||||
return len < o.len;
|
||||
return std::memcmp((const uint8_t*)get(), (const uint8_t*)o.get(), len) < 0;
|
||||
}
|
||||
|
||||
bool equals(const SockAddr& o) const {
|
||||
return second == o.second
|
||||
&& std::memcmp((uint8_t*)&first, (uint8_t*)&o.first, second) == 0;
|
||||
return len == o.len
|
||||
&& std::memcmp((const uint8_t*)get(), (const uint8_t*)o.get(), len) == 0;
|
||||
}
|
||||
SockAddr& operator=(const SockAddr& o) {
|
||||
std::copy_n((const uint8_t*)&o.first, o.second, (uint8_t*)&first);
|
||||
second = o.second;
|
||||
set(o.get(), o.getLength());
|
||||
return *this;
|
||||
}
|
||||
SockAddr& operator=(SockAddr&& o) {
|
||||
len = o.len;
|
||||
o.len = 0;
|
||||
addr = std::move(o.addr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
return print_addr(first, second);
|
||||
}
|
||||
sa_family_t getFamily() const { return second > sizeof(sa_family_t) ? first.ss_family : AF_UNSPEC; }
|
||||
void setFamily(sa_family_t af) {
|
||||
first.ss_family = af;
|
||||
switch(af) {
|
||||
case AF_INET:
|
||||
second = sizeof(sockaddr_in);
|
||||
break;
|
||||
case AF_INET6:
|
||||
second = sizeof(sockaddr_in6);
|
||||
break;
|
||||
}
|
||||
return print_addr(get(), getLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address family or AF_UNSPEC if the address is not set.
|
||||
*/
|
||||
sa_family_t getFamily() const { return len > sizeof(sa_family_t) ? addr->sa_family : AF_UNSPEC; }
|
||||
|
||||
/**
|
||||
* Resize the managed structure to the appropriate size (if needed),
|
||||
* in which case the sockaddr structure is cleared to zero,
|
||||
* and set the address family field (sa_family).
|
||||
*/
|
||||
void setFamily(sa_family_t af) {
|
||||
socklen_t new_length;
|
||||
switch(af) {
|
||||
case AF_INET:
|
||||
new_length = sizeof(sockaddr_in);
|
||||
break;
|
||||
case AF_INET6:
|
||||
new_length = sizeof(sockaddr_in6);
|
||||
break;
|
||||
default:
|
||||
new_length = 0;
|
||||
}
|
||||
if (new_length != len) {
|
||||
len = new_length;
|
||||
if (len) addr.reset((sockaddr*)::calloc(len, 1));
|
||||
else addr.reset();
|
||||
}
|
||||
if (len > sizeof(sa_family_t))
|
||||
addr->sa_family = af;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreive the port (in host byte order) or 0 if the address is not
|
||||
* of a supported family.
|
||||
*/
|
||||
in_port_t getPort() const {
|
||||
switch(getFamily()) {
|
||||
case AF_INET:
|
||||
@@ -94,6 +160,10 @@ public:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the port. The address must be of a supported family.
|
||||
* @param p The port in host byte order.
|
||||
*/
|
||||
void setPort(in_port_t p) {
|
||||
switch(getFamily()) {
|
||||
case AF_INET:
|
||||
@@ -105,17 +175,42 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accessible byte length at the pointer returned by #get().
|
||||
* If zero, #get() returns null.
|
||||
*/
|
||||
socklen_t getLength() const { return len; }
|
||||
|
||||
/**
|
||||
* An address is defined to be true if its length is not zero.
|
||||
*/
|
||||
explicit operator bool() const noexcept {
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address to the managed sockaddr structure.
|
||||
* The accessible length is returned by #getLength().
|
||||
*/
|
||||
const sockaddr* get() const { return addr.get(); }
|
||||
|
||||
/**
|
||||
* Returns the address to the managed sockaddr structure.
|
||||
* The accessible length is returned by #getLength().
|
||||
*/
|
||||
sockaddr* get() { return addr.get(); }
|
||||
|
||||
const sockaddr_in& getIPv4() const {
|
||||
return *reinterpret_cast<const sockaddr_in*>(&first);
|
||||
return *reinterpret_cast<const sockaddr_in*>(get());
|
||||
}
|
||||
const sockaddr_in6& getIPv6() const {
|
||||
return *reinterpret_cast<const sockaddr_in6*>(&first);
|
||||
return *reinterpret_cast<const sockaddr_in6*>(get());
|
||||
}
|
||||
sockaddr_in& getIPv4() {
|
||||
return *reinterpret_cast<sockaddr_in*>(&first);
|
||||
return *reinterpret_cast<sockaddr_in*>(get());
|
||||
}
|
||||
sockaddr_in6& getIPv6() {
|
||||
return *reinterpret_cast<sockaddr_in6*>(&first);
|
||||
return *reinterpret_cast<sockaddr_in6*>(get());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,14 +225,17 @@ public:
|
||||
|
||||
bool isUnspecified() const;
|
||||
|
||||
bool isMappedIPv4() const;
|
||||
SockAddr getMappedIPv4() const;
|
||||
|
||||
/**
|
||||
* A comparator to classify IP addresses, only considering the
|
||||
* first 64 bits in IPv6.
|
||||
*/
|
||||
struct ipCmp {
|
||||
bool operator()(const SockAddr& a, const SockAddr& b) const {
|
||||
if (a.second != b.second)
|
||||
return a.second < b.second;
|
||||
if (a.len != b.len)
|
||||
return a.len < b.len;
|
||||
socklen_t start, len;
|
||||
switch(a.getFamily()) {
|
||||
case AF_INET:
|
||||
@@ -151,18 +249,30 @@ public:
|
||||
break;
|
||||
default:
|
||||
start = 0;
|
||||
len = a.second;
|
||||
len = a.len;
|
||||
break;
|
||||
}
|
||||
|
||||
return std::memcmp((uint8_t*)&a.first+start, (uint8_t*)&b.first+start, len) < 0;
|
||||
return std::memcmp((uint8_t*)a.get()+start,
|
||||
(uint8_t*)b.get()+start, len) < 0;
|
||||
}
|
||||
};
|
||||
private:
|
||||
socklen_t len {0};
|
||||
struct free_delete { void operator()(void* p) { ::free(p); } };
|
||||
std::unique_ptr<sockaddr, free_delete> addr {};
|
||||
|
||||
void set(const sockaddr* sa, socklen_t length) {
|
||||
if (len != length) {
|
||||
len = length;
|
||||
if (len) addr.reset((sockaddr*)::malloc(len));
|
||||
else addr.reset();
|
||||
}
|
||||
if (len)
|
||||
std::memcpy((uint8_t*)get(), (const uint8_t*)sa, len);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
OPENDHT_PUBLIC bool operator==(const SockAddr& a, const SockAddr& b);
|
||||
|
||||
OPENDHT_PUBLIC std::string printAddr(const SockAddr& addr);
|
||||
|
||||
}
|
||||
+15
-3
@@ -32,6 +32,9 @@
|
||||
#define WANT4 1
|
||||
#define WANT6 2
|
||||
|
||||
/**
|
||||
* OpenDHT C++ namespace
|
||||
*/
|
||||
namespace dht {
|
||||
|
||||
using NetId = uint32_t;
|
||||
@@ -52,9 +55,15 @@ void erase_if(std::map<Key, Item>& map, const Condition& condition)
|
||||
}
|
||||
|
||||
class OPENDHT_PUBLIC DhtException : public std::runtime_error {
|
||||
public:
|
||||
DhtException(const std::string &str = "") :
|
||||
std::runtime_error("DhtException occurred: " + str) {}
|
||||
public:
|
||||
DhtException(const std::string &str = "") :
|
||||
std::runtime_error("DhtException occurred: " + str) {}
|
||||
};
|
||||
|
||||
class OPENDHT_PUBLIC SocketException : public DhtException {
|
||||
public:
|
||||
SocketException(int err) :
|
||||
DhtException(strerror(err)) {}
|
||||
};
|
||||
|
||||
// Time related definitions and utility functions
|
||||
@@ -93,6 +102,9 @@ public:
|
||||
|
||||
// Serialization related definitions and utility functions
|
||||
|
||||
/**
|
||||
* Arbitrary binary data.
|
||||
*/
|
||||
using Blob = std::vector<uint8_t>;
|
||||
|
||||
/**
|
||||
|
||||
+31
-6
@@ -22,6 +22,7 @@
|
||||
#include "infohash.h"
|
||||
#include "crypto.h"
|
||||
#include "utils.h"
|
||||
#include "sockaddr.h"
|
||||
|
||||
#include <msgpack.hpp>
|
||||
|
||||
@@ -36,6 +37,10 @@
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
#include <json/json.h>
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
namespace dht {
|
||||
|
||||
struct Value;
|
||||
@@ -51,7 +56,7 @@ struct Query;
|
||||
* @param form_addr: network address of the incoming request.
|
||||
* @param from_len: network address lendth of the incoming request.
|
||||
*/
|
||||
using StorePolicy = std::function<bool(InfoHash key, std::shared_ptr<Value>& value, InfoHash from, const sockaddr* from_addr, socklen_t from_len)>;
|
||||
using StorePolicy = std::function<bool(InfoHash key, std::shared_ptr<Value>& value, const InfoHash& from, const SockAddr& addr)>;
|
||||
|
||||
/**
|
||||
* An edition policy is applied once to every incoming value storage requests,
|
||||
@@ -67,15 +72,15 @@ using StorePolicy = std::function<bool(InfoHash key, std::shared_ptr<Value>& val
|
||||
* @param form_addr: network address of the incoming request.
|
||||
* @param from_len: network address lendth of the incoming request.
|
||||
*/
|
||||
using EditPolicy = std::function<bool(InfoHash key, const std::shared_ptr<Value>& old_val, std::shared_ptr<Value>& new_val, InfoHash from, const sockaddr* from_addr, socklen_t from_len)>;
|
||||
using EditPolicy = std::function<bool(InfoHash key, const std::shared_ptr<Value>& old_val, std::shared_ptr<Value>& new_val, const InfoHash& from, const SockAddr& addr)>;
|
||||
|
||||
static constexpr const size_t MAX_VALUE_SIZE {1024 * 64};
|
||||
|
||||
struct OPENDHT_PUBLIC ValueType {
|
||||
typedef uint16_t Id;
|
||||
|
||||
static bool DEFAULT_STORE_POLICY(InfoHash, std::shared_ptr<Value>& v, InfoHash, const sockaddr*, socklen_t);
|
||||
static bool DEFAULT_EDIT_POLICY(InfoHash, const std::shared_ptr<Value>&, std::shared_ptr<Value>&, InfoHash, const sockaddr*, socklen_t) {
|
||||
static bool DEFAULT_STORE_POLICY(InfoHash, std::shared_ptr<Value>& v, const InfoHash&, const SockAddr&);
|
||||
static bool DEFAULT_EDIT_POLICY(InfoHash, const std::shared_ptr<Value>&, std::shared_ptr<Value>&, const InfoHash&, const SockAddr&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -345,6 +350,14 @@ struct OPENDHT_PUBLIC Value
|
||||
Value(ValueType::Id t, const uint8_t* dat_ptr, size_t dat_len, Id id = INVALID_ID)
|
||||
: id(id), type(t), data(dat_ptr, dat_ptr+dat_len) {}
|
||||
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
/**
|
||||
* Build a value from a json object
|
||||
* @param json
|
||||
*/
|
||||
Value(Json::Value& json);
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
template <typename Type>
|
||||
Value(ValueType::Id t, const Type& d, Id id = INVALID_ID)
|
||||
: id(id), type(t), data(packMsg(d)) {}
|
||||
@@ -416,6 +429,18 @@ struct OPENDHT_PUBLIC Value
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
/**
|
||||
* Build a json object from a value
|
||||
* Example:
|
||||
* {
|
||||
* "data":"base64ofdata",
|
||||
* id":"0", "seq":0,"type":3
|
||||
* }
|
||||
*/
|
||||
Json::Value toJson() const;
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
/** Return the size in bytes used by this value in memory (minimum). */
|
||||
size_t size() const;
|
||||
|
||||
@@ -423,11 +448,11 @@ struct OPENDHT_PUBLIC Value
|
||||
void msgpack_pack_to_sign(Packer& pk) const
|
||||
{
|
||||
bool has_owner = owner && *owner;
|
||||
pk.pack_map((user_type.empty()?0:1) + (has_owner?(recipient == InfoHash() ? 4 : 5):2));
|
||||
pk.pack_map((user_type.empty()?0:1) + (has_owner?(recipient ? 5 : 4):2));
|
||||
if (has_owner) { // isSigned
|
||||
pk.pack(std::string("seq")); pk.pack(seq);
|
||||
pk.pack(std::string("owner")); owner->msgpack_pack(pk);
|
||||
if (recipient != InfoHash()) {
|
||||
if (recipient) {
|
||||
pk.pack(std::string("to")); pk.pack(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-7
@@ -92,9 +92,9 @@ cdef class _WithID(object):
|
||||
cdef class InfoHash(_WithID):
|
||||
cdef cpp.InfoHash _infohash
|
||||
def __cinit__(self, bytes str=b''):
|
||||
self._infohash = cpp.InfoHash(str)
|
||||
self._infohash = cpp.InfoHash(str) if str else cpp.InfoHash()
|
||||
def __bool__(InfoHash self):
|
||||
return not (self._infohash == cpp.InfoHash())
|
||||
return <bool>self._infohash
|
||||
def __richcmp__(InfoHash self, InfoHash other, int op):
|
||||
if op == 0:
|
||||
return self._infohash < other._infohash
|
||||
@@ -179,8 +179,11 @@ cdef class Query(object):
|
||||
|
||||
cdef class Select(object):
|
||||
cdef cpp.Select _select
|
||||
def __cinit__(self, str q_str=''):
|
||||
self._select = cpp.Select(q_str.encode())
|
||||
def __cinit__(self, str q_str=None):
|
||||
if q_str:
|
||||
self._select = cpp.Select(q_str.encode())
|
||||
else:
|
||||
self._select = cpp.Select()
|
||||
def __str__(self):
|
||||
return self._select.toString().decode()
|
||||
def isSatisfiedBy(self, Select os):
|
||||
@@ -191,8 +194,11 @@ cdef class Select(object):
|
||||
|
||||
cdef class Where(object):
|
||||
cdef cpp.Where _where
|
||||
def __cinit__(self, str q_str=''):
|
||||
self._where = cpp.Where(q_str.encode())
|
||||
def __cinit__(self, str q_str=None):
|
||||
if q_str:
|
||||
self._where = cpp.Where(q_str.encode())
|
||||
else:
|
||||
self._where = cpp.Where()
|
||||
def __str__(self):
|
||||
return self._where.toString().decode()
|
||||
def isSatisfiedBy(self, Where where):
|
||||
@@ -527,6 +533,8 @@ cdef class DhtRunner(_WithID):
|
||||
if get_cb:
|
||||
cb_obj = {'get':get_cb, 'done':done_cb, 'filter':filter}
|
||||
ref.Py_INCREF(cb_obj)
|
||||
if where is None:
|
||||
where = Where()
|
||||
self.thisptr.get().get(key._infohash, cpp.bindGetCb(get_callback, <void*>cb_obj),
|
||||
cpp.bindDoneCb(done_callback, <void*>cb_obj),
|
||||
cpp.nullptr, #filter implemented in the get_callback
|
||||
@@ -654,4 +662,3 @@ cdef class Pht(object):
|
||||
val,
|
||||
cpp.bindDoneCbSimple(done_callback_simple, <void*>cb_obj)
|
||||
)
|
||||
|
||||
@@ -65,8 +65,9 @@ cdef extern from "opendht/infohash.h" namespace "dht":
|
||||
InfoHash get(string s)
|
||||
@staticmethod
|
||||
InfoHash getRandom()
|
||||
bool operator==(InfoHash)
|
||||
bool operator<(InfoHash)
|
||||
bool operator==(InfoHash) const
|
||||
bool operator<(InfoHash) const
|
||||
bool operator bool() const
|
||||
|
||||
cdef extern from "opendht/sockaddr.h" namespace "dht":
|
||||
cdef cppclass SockAddr:
|
||||
@@ -259,4 +260,3 @@ cdef extern from "opendht/indexation/pht.h" namespace "dht::indexation":
|
||||
void insert(IndexKey k, IndexValue v, DoneCallbackSimple cb)
|
||||
@staticmethod
|
||||
LookupCallback bindLookupCb(LookupCallbackRaw cb, void *user_data)
|
||||
|
||||
+12
-2
@@ -9,6 +9,8 @@ libopendht_la_SOURCES = \
|
||||
listener.h \
|
||||
request.h \
|
||||
search.h \
|
||||
net.h \
|
||||
parsed_message.h \
|
||||
node_cache.cpp \
|
||||
callbacks.cpp \
|
||||
routing_table.cpp \
|
||||
@@ -36,7 +38,6 @@ nobase_include_HEADERS = \
|
||||
../include/opendht/node_cache.h \
|
||||
../include/opendht/routing_table.h \
|
||||
../include/opendht/network_engine.h \
|
||||
../include/opendht/net.h \
|
||||
../include/opendht/scheduler.h \
|
||||
../include/opendht/rate_limiter.h \
|
||||
../include/opendht/utils.h \
|
||||
@@ -53,6 +54,16 @@ nobase_include_HEADERS = \
|
||||
../include/opendht/rng.h \
|
||||
../include/opendht/indexation/pht.h
|
||||
|
||||
if ENABLE_PROXY_SERVER
|
||||
libopendht_la_SOURCES += base64.h base64.cpp dht_proxy_server.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/dht_proxy_server.h
|
||||
endif
|
||||
|
||||
if ENABLE_PROXY_CLIENT
|
||||
libopendht_la_SOURCES += dht_proxy_client.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/dht_proxy_client.h ../include/opendht/dht_interface.h
|
||||
endif
|
||||
|
||||
clean-local:
|
||||
rm -rf libargon2.la
|
||||
|
||||
@@ -82,4 +93,3 @@ noinst_HEADERS = \
|
||||
@top_builddir@/argon2/src/encoding.h \
|
||||
@top_builddir@/argon2/src/thread.h
|
||||
endif
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2017 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Mainly based on the following stackoverflow question:
|
||||
* http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c
|
||||
*/
|
||||
static const char encoding_table[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
|
||||
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
|
||||
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
||||
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
|
||||
'3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||
};
|
||||
|
||||
static const size_t mod_table[] = { 0, 2, 1 };
|
||||
|
||||
char *base64_encode(const uint8_t *input, size_t input_length,
|
||||
char *output, size_t *output_length)
|
||||
{
|
||||
size_t i, j;
|
||||
size_t out_sz = *output_length;
|
||||
*output_length = 4 * ((input_length + 2) / 3);
|
||||
if (out_sz < *output_length || output == nullptr)
|
||||
return nullptr;
|
||||
|
||||
for (i = 0, j = 0; i < input_length; ) {
|
||||
uint8_t octet_a = i < input_length ? input[i++] : 0;
|
||||
uint8_t octet_b = i < input_length ? input[i++] : 0;
|
||||
uint8_t octet_c = i < input_length ? input[i++] : 0;
|
||||
|
||||
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
|
||||
output[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
|
||||
output[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
|
||||
output[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
|
||||
output[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
|
||||
}
|
||||
|
||||
for (i = 0; i < mod_table[input_length % 3]; i++)
|
||||
output[*output_length - 1 - i] = '=';
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
uint8_t *base64_decode(const char *input, size_t input_length,
|
||||
uint8_t *output, size_t *output_length)
|
||||
{
|
||||
size_t i, j;
|
||||
uint8_t decoding_table[256];
|
||||
|
||||
uint8_t c;
|
||||
for (c = 0; c < 64; c++)
|
||||
decoding_table[static_cast<int>(encoding_table[c])] = c;
|
||||
|
||||
if (input_length % 4 != 0 || input_length < 2)
|
||||
return nullptr;
|
||||
|
||||
size_t out_sz = *output_length;
|
||||
*output_length = input_length / 4 * 3;
|
||||
if (input[input_length - 1] == '=')
|
||||
(*output_length)--;
|
||||
if (input[input_length - 2] == '=')
|
||||
(*output_length)--;
|
||||
|
||||
if (out_sz < *output_length || output == nullptr)
|
||||
return nullptr;
|
||||
|
||||
for (i = 0, j = 0; i < input_length;) {
|
||||
uint8_t sextet_a = input[i] == '=' ? 0 & i++
|
||||
: decoding_table[static_cast<int>(input[i++])];
|
||||
uint8_t sextet_b = input[i] == '=' ? 0 & i++
|
||||
: decoding_table[static_cast<int>(input[i++])];
|
||||
uint8_t sextet_c = input[i] == '=' ? 0 & i++
|
||||
: decoding_table[static_cast<int>(input[i++])];
|
||||
uint8_t sextet_d = input[i] == '=' ? 0 & i++
|
||||
: decoding_table[static_cast<int>(input[i++])];
|
||||
|
||||
uint32_t triple = (sextet_a << 3 * 6) +
|
||||
(sextet_b << 2 * 6) +
|
||||
(sextet_c << 1 * 6) +
|
||||
(sextet_d << 0 * 6);
|
||||
|
||||
if (j < *output_length)
|
||||
output[j++] = (triple >> 2 * 8) & 0xFF;
|
||||
if (j < *output_length)
|
||||
output[j++] = (triple >> 1 * 8) & 0xFF;
|
||||
if (j < *output_length)
|
||||
output[j++] = (triple >> 0 * 8) & 0xFF;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string
|
||||
base64_encode(const std::vector<uint8_t>::const_iterator begin,
|
||||
const std::vector<uint8_t>::const_iterator end)
|
||||
{
|
||||
size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3);
|
||||
std::string out;
|
||||
out.resize(output_length);
|
||||
base64_encode(&(*begin), std::distance(begin, end),
|
||||
&(*out.begin()), &output_length);
|
||||
out.resize(output_length);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
base64_encode(const std::vector<unsigned char>& str)
|
||||
{
|
||||
return base64_encode(str.cbegin(), str.cend());
|
||||
}
|
||||
|
||||
std::string
|
||||
base64_decode(const std::string& str)
|
||||
{
|
||||
size_t output_length = str.length() / 4 * 3 + 2;
|
||||
std::vector<uint8_t> output;
|
||||
output.resize(output_length);
|
||||
base64_decode(str.data(), str.size(), output.data(), &output_length);
|
||||
output.resize(output_length);
|
||||
return std::string(output.begin(), output.end());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2017 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Encode a buffer in base64.
|
||||
*
|
||||
* @param str the input buffer
|
||||
* @return a base64-encoded buffer
|
||||
*/
|
||||
std::string base64_encode(const std::vector<unsigned char>& str);
|
||||
/**
|
||||
* Decode a buffer in base64.
|
||||
*
|
||||
* @param str the input buffer
|
||||
* @return a base64-decoded buffer
|
||||
*/
|
||||
std::string base64_decode(const std::string& str);
|
||||
@@ -68,4 +68,36 @@ NodeStats::toString() const
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
/**
|
||||
* Build a json object from a NodeStats
|
||||
*/
|
||||
Json::Value
|
||||
NodeStats::toJson() const
|
||||
{
|
||||
Json::Value val;
|
||||
val["good"] = static_cast<Json::LargestUInt>(good_nodes);
|
||||
val["dubious"] = static_cast<Json::LargestUInt>(dubious_nodes);
|
||||
val["incoming"] = static_cast<Json::LargestUInt>(incoming_nodes);
|
||||
if (table_depth > 1) {
|
||||
val["table_depth"] = static_cast<Json::LargestUInt>(table_depth);
|
||||
unsigned long tot_nodes = 8 * std::exp2(table_depth);
|
||||
val["network_size_estimation"] = static_cast<Json::LargestUInt>(tot_nodes);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
NodeStats::NodeStats(const Json::Value& val)
|
||||
{
|
||||
if (val.isMember("good"))
|
||||
good_nodes = static_cast<unsigned>(val["good"].asLargestUInt());
|
||||
if (val.isMember("dubious"))
|
||||
dubious_nodes = static_cast<unsigned>(val["dubious"].asLargestUInt());
|
||||
if (val.isMember("incoming"))
|
||||
incoming_nodes = static_cast<unsigned>(val["incoming"].asLargestUInt());
|
||||
if (val.isMember("table_depth"))
|
||||
table_depth = static_cast<unsigned>(val["table_depth"].asLargestUInt());
|
||||
}
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
}
|
||||
+36
-5
@@ -84,7 +84,7 @@ static constexpr size_t PASSWORD_SALT_LENGTH {16};
|
||||
|
||||
constexpr gnutls_digest_algorithm_t gnutlsHashAlgo(size_t min_res) {
|
||||
return (min_res > 256/8) ? GNUTLS_DIG_SHA512 : (
|
||||
(min_res > 128/8) ? GNUTLS_DIG_SHA256 : (
|
||||
(min_res > 160/8) ? GNUTLS_DIG_SHA256 : (
|
||||
GNUTLS_DIG_SHA1));
|
||||
}
|
||||
|
||||
@@ -212,12 +212,21 @@ Blob hash(const Blob& data, size_t hash_len)
|
||||
Blob res;
|
||||
res.resize(res_size);
|
||||
const gnutls_datum_t gdat {(uint8_t*)data.data(), (unsigned)data.size()};
|
||||
if (gnutls_fingerprint(algo, &gdat, res.data(), &res_size))
|
||||
throw CryptoException("Can't compute hash !");
|
||||
if (auto err = gnutls_fingerprint(algo, &gdat, res.data(), &res_size))
|
||||
throw CryptoException(std::string("Can't compute hash: ") + gnutls_strerror(err));
|
||||
res.resize(std::min(hash_len, res_size));
|
||||
return res;
|
||||
}
|
||||
|
||||
void hash(const uint8_t* data, size_t data_length, uint8_t* hash, size_t hash_length)
|
||||
{
|
||||
auto algo = gnutlsHashAlgo(hash_length);
|
||||
size_t res_size = hash_length;
|
||||
const gnutls_datum_t gdat {(uint8_t*)data, (unsigned)data_length};
|
||||
if (auto err = gnutls_fingerprint(algo, &gdat, hash, &res_size))
|
||||
throw CryptoException(std::string("Can't compute hash: ") + gnutls_strerror(err));
|
||||
}
|
||||
|
||||
PrivateKey::PrivateKey()
|
||||
{}
|
||||
|
||||
@@ -529,7 +538,12 @@ PublicKey::getId() const
|
||||
return {};
|
||||
InfoHash id;
|
||||
size_t sz = id.size();
|
||||
if (auto err = gnutls_pubkey_get_key_id(pk, 0, id.data(), &sz))
|
||||
#if GNUTLS_VERSION_NUMBER < 0x030401
|
||||
const int flags = 0;
|
||||
#else
|
||||
const int flags = (id.size() == 32) ? GNUTLS_KEYID_USE_SHA256 : 0;
|
||||
#endif
|
||||
if (auto err = gnutls_pubkey_get_key_id(pk, flags, id.data(), &sz))
|
||||
throw CryptoException(std::string("Can't get public key ID: ") + gnutls_strerror(err));
|
||||
if (sz != id.size())
|
||||
throw CryptoException("Can't get public key ID: wrong output length.");
|
||||
@@ -882,6 +896,19 @@ generateIdentity(const std::string& name, Identity ca, unsigned key_length) {
|
||||
return generateIdentity(name, ca, key_length, !ca.first || !ca.second);
|
||||
}
|
||||
|
||||
Identity
|
||||
generateEcIdentity(const std::string& name, crypto::Identity ca, bool is_ca)
|
||||
{
|
||||
auto key = std::make_shared<PrivateKey>(PrivateKey::generateEC());
|
||||
auto cert = std::make_shared<Certificate>(Certificate::generate(*key, name, ca, is_ca));
|
||||
return {std::move(key), std::move(cert)};
|
||||
}
|
||||
|
||||
Identity
|
||||
generateEcIdentity(const std::string& name, Identity ca) {
|
||||
return generateEcIdentity(name, ca, !ca.first || !ca.second);
|
||||
}
|
||||
|
||||
Certificate
|
||||
Certificate::generate(const PrivateKey& key, const std::string& name, Identity ca, bool is_ca)
|
||||
{
|
||||
@@ -916,10 +943,12 @@ Certificate::generate(const PrivateKey& key, const std::string& name, Identity c
|
||||
gnutls_x509_crt_set_serial(cert, &cert_serial, sizeof(cert_serial));
|
||||
}
|
||||
|
||||
unsigned key_usage = GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_DATA_ENCIPHERMENT;
|
||||
unsigned key_usage = 0;
|
||||
if (is_ca) {
|
||||
gnutls_x509_crt_set_ca_status(cert, 1);
|
||||
key_usage |= GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN;
|
||||
} else {
|
||||
key_usage |= GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_DATA_ENCIPHERMENT;
|
||||
}
|
||||
gnutls_x509_crt_set_key_usage(cert, key_usage);
|
||||
|
||||
@@ -1262,8 +1291,10 @@ std::ostream& operator<< (std::ostream& o, const TrustList::VerifyResult& h)
|
||||
o << "* Certificate has expired" << std::endl;
|
||||
if (h.result & GNUTLS_CERT_UNEXPECTED_OWNER)
|
||||
o << "* The owner is not the expected one" << std::endl;
|
||||
#if GNUTLS_VERSION_NUMBER >= 0x030401
|
||||
if (h.result & GNUTLS_CERT_PURPOSE_MISMATCH)
|
||||
o << "* Certificate or an intermediate does not match the intended purpose" << std::endl;
|
||||
#endif
|
||||
if (h.result & GNUTLS_CERT_MISMATCH)
|
||||
o << "* Certificate presented isn't the expected one" << std::endl;
|
||||
} else {
|
||||
|
||||
+9
-10
@@ -27,14 +27,14 @@ std::ostream& operator<< (std::ostream& s, const DhtMessage& v)
|
||||
}
|
||||
|
||||
bool
|
||||
DhtMessage::storePolicy(InfoHash h, std::shared_ptr<Value>& v, InfoHash f, const sockaddr* fa, socklen_t fas)
|
||||
DhtMessage::storePolicy(InfoHash h, std::shared_ptr<Value>& v, const InfoHash& f, const SockAddr& sa)
|
||||
{
|
||||
try {
|
||||
auto msg = unpackMsg<DhtMessage>(v->data);
|
||||
if (msg.service.empty())
|
||||
return false;
|
||||
} catch (const std::exception& e) {}
|
||||
return ValueType::DEFAULT_STORE_POLICY(h, v, f, fa, fas);
|
||||
return ValueType::DEFAULT_STORE_POLICY(h, v, f, sa);
|
||||
}
|
||||
|
||||
Value::Filter
|
||||
@@ -54,12 +54,11 @@ DhtMessage::ServiceFilter(std::string s)
|
||||
|
||||
std::ostream& operator<< (std::ostream& s, const IpServiceAnnouncement& v)
|
||||
{
|
||||
s << "Peer: ";
|
||||
s << "port " << v.getPort();
|
||||
|
||||
if (v.ss.ss_family == AF_INET || v.ss.ss_family == AF_INET6) {
|
||||
if (v.addr) {
|
||||
s << "Peer: ";
|
||||
s << "port " << v.getPort();
|
||||
char hbuf[NI_MAXHOST];
|
||||
if (getnameinfo((sockaddr*)&v.ss, sizeof(v.ss), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST) == 0) {
|
||||
if (getnameinfo(v.addr.get(), v.addr.getLength(), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST) == 0) {
|
||||
s << " addr " << std::string(hbuf, strlen(hbuf));
|
||||
}
|
||||
}
|
||||
@@ -67,17 +66,17 @@ std::ostream& operator<< (std::ostream& s, const IpServiceAnnouncement& v)
|
||||
}
|
||||
|
||||
bool
|
||||
IpServiceAnnouncement::storePolicy(InfoHash h, std::shared_ptr<Value>& v, InfoHash f, const sockaddr* from, socklen_t fromlen)
|
||||
IpServiceAnnouncement::storePolicy(InfoHash h, std::shared_ptr<Value>& v, const InfoHash& f, const SockAddr& sa)
|
||||
{
|
||||
try {
|
||||
auto msg = unpackMsg<IpServiceAnnouncement>(v->data);
|
||||
if (msg.getPort() == 0)
|
||||
return false;
|
||||
IpServiceAnnouncement sa_addr {from, fromlen};
|
||||
IpServiceAnnouncement sa_addr {sa};
|
||||
sa_addr.setPort(msg.getPort());
|
||||
// argument v is modified (not the value).
|
||||
v = std::make_shared<Value>(IpServiceAnnouncement::TYPE, sa_addr, v->id);
|
||||
return ValueType::DEFAULT_STORE_POLICY(h, v, f, from, fromlen);
|
||||
return ValueType::DEFAULT_STORE_POLICY(h, v, f, sa);
|
||||
} catch (const std::exception& e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
+275
-385
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2018 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
* Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
|
||||
#include "dht_proxy_client.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <json/json.h>
|
||||
#include <restbed>
|
||||
#include <vector>
|
||||
|
||||
#include "dhtrunner.h"
|
||||
|
||||
constexpr const char* const HTTP_PROTO {"http://"};
|
||||
|
||||
namespace dht {
|
||||
|
||||
DhtProxyClient::DhtProxyClient(std::function<void()> signal, const std::string& serverHost, const std::string& pushClientId)
|
||||
: serverHost_(serverHost), pushClientId_(pushClientId), scheduler(DHT_LOG), loopSignal_(signal)
|
||||
{
|
||||
if (!serverHost_.empty())
|
||||
startProxy();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::confirmProxy()
|
||||
{
|
||||
if (serverHost_.empty()) return;
|
||||
getConnectivityStatus();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::startProxy()
|
||||
{
|
||||
if (serverHost_.empty()) return;
|
||||
DHT_LOG.WARN("Staring proxy client to %s", serverHost_.c_str());
|
||||
nextProxyConfirmation = scheduler.add(scheduler.time(), std::bind(&DhtProxyClient::confirmProxy, this));
|
||||
}
|
||||
|
||||
DhtProxyClient::~DhtProxyClient()
|
||||
{
|
||||
cancelAllOperations();
|
||||
cancelAllListeners();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::cancelAllOperations()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockOperations_);
|
||||
auto operation = operations_.begin();
|
||||
while (operation != operations_.end()) {
|
||||
if (operation->thread.joinable()) {
|
||||
// Close connection to stop operation?
|
||||
restbed::Http::close(operation->req);
|
||||
operation->thread.join();
|
||||
operation = operations_.erase(operation);
|
||||
} else {
|
||||
++operation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::cancelAllListeners()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
for (auto& listener: listeners_) {
|
||||
if (listener.thread.joinable()) {
|
||||
// Close connection to stop listener?
|
||||
if (listener.req)
|
||||
restbed::Http::close(listener.req);
|
||||
listener.thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::shutdown(ShutdownCallback cb)
|
||||
{
|
||||
cancelAllOperations();
|
||||
cancelAllListeners();
|
||||
if (cb)
|
||||
cb();
|
||||
}
|
||||
|
||||
NodeStatus
|
||||
DhtProxyClient::getStatus(sa_family_t af) const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
|
||||
switch (af)
|
||||
{
|
||||
case AF_INET:
|
||||
return statusIpv4_;
|
||||
case AF_INET6:
|
||||
return statusIpv6_;
|
||||
default:
|
||||
return NodeStatus::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
DhtProxyClient::isRunning(sa_family_t af) const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
|
||||
switch (af)
|
||||
{
|
||||
case AF_INET:
|
||||
return statusIpv4_ != NodeStatus::Disconnected;
|
||||
case AF_INET6:
|
||||
return statusIpv6_ != NodeStatus::Disconnected;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
time_point
|
||||
DhtProxyClient::periodic(const uint8_t*, size_t, const SockAddr&)
|
||||
{
|
||||
// Exec all currently stored callbacks
|
||||
scheduler.syncTime();
|
||||
if (!callbacks_.empty()) {
|
||||
std::lock_guard<std::mutex> lock(lockCallbacks);
|
||||
for (auto& callback : callbacks_)
|
||||
callback();
|
||||
callbacks_.clear();
|
||||
}
|
||||
// Remove finished operations
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockOperations_);
|
||||
auto operation = operations_.begin();
|
||||
while (operation != operations_.end()) {
|
||||
if (*(operation->finished)) {
|
||||
if (operation->thread.joinable()) {
|
||||
// Close connection to stop operation?
|
||||
restbed::Http::close(operation->req);
|
||||
operation->thread.join();
|
||||
}
|
||||
operation = operations_.erase(operation);
|
||||
} else {
|
||||
++operation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scheduler.run();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::get(const InfoHash& key, const GetCallback& cb, DoneCallback donecb, const Value::Filter& filterChain)
|
||||
{
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/" + key.toString());
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
|
||||
auto finished = std::make_shared<std::atomic_bool>(false);
|
||||
Operation o;
|
||||
o.req = req;
|
||||
o.finished = finished;
|
||||
o.thread = std::thread([=](){
|
||||
// Try to contact the proxy and set the status to connected when done.
|
||||
// will change the connectivity status
|
||||
auto ok = std::make_shared<std::atomic_bool>(true);
|
||||
restbed::Http::async(req,
|
||||
[=](const std::shared_ptr<restbed::Request>& req,
|
||||
const std::shared_ptr<restbed::Response>& reply) {
|
||||
auto code = reply->get_status_code();
|
||||
|
||||
if (code == 200) {
|
||||
try {
|
||||
while (restbed::Http::is_open(req) and not *finished) {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
if (*finished)
|
||||
break;
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
reply->set_body(""); // Reset the body for the next fetch
|
||||
|
||||
std::string err;
|
||||
Json::Value json;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&body[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (reader->parse(char_data, char_data + body.size(), &json, &err)) {
|
||||
auto value = std::make_shared<Value>(json);
|
||||
if ((not filterChain or filterChain(*value)) && cb) {
|
||||
std::lock_guard<std::mutex> lock(lockCallbacks);
|
||||
callbacks_.emplace_back([cb, value, finished]() {
|
||||
if (not cb({value}))
|
||||
*finished = true;
|
||||
});
|
||||
loopSignal_();
|
||||
}
|
||||
} else {
|
||||
*ok = false;
|
||||
}
|
||||
}
|
||||
} catch (std::runtime_error& e) { }
|
||||
} else {
|
||||
*ok = false;
|
||||
}
|
||||
}).wait();
|
||||
if (donecb) {
|
||||
std::lock_guard<std::mutex> lock(lockCallbacks);
|
||||
callbacks_.emplace_back([=](){
|
||||
donecb(*ok, {});
|
||||
});
|
||||
loopSignal_();
|
||||
}
|
||||
if (!ok) {
|
||||
// Connection failed, update connectivity
|
||||
opFailed();
|
||||
}
|
||||
*finished = true;
|
||||
});
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockOperations_);
|
||||
operations_.emplace_back(std::move(o));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb,
|
||||
Value::Filter&& filter, Where&& where)
|
||||
{
|
||||
Query query {{}, where};
|
||||
auto filterChain = filter.chain(query.where.getFilter());
|
||||
get(key, cb, donecb, filterChain);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_point, bool permanent)
|
||||
{
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/" + key.toString());
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
req->set_method("POST");
|
||||
auto json = val->toJson();
|
||||
if (permanent)
|
||||
json["permanent"] = true;
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto body = Json::writeString(wbuilder, json) + "\n";
|
||||
req->set_body(body);
|
||||
req->set_header("Content-Length", std::to_string(body.size()));
|
||||
|
||||
auto finished = std::make_shared<std::atomic_bool>(false);
|
||||
Operation o;
|
||||
o.req = req;
|
||||
o.finished = finished;
|
||||
o.thread = std::thread([=](){
|
||||
auto ok = std::make_shared<std::atomic_bool>(true);
|
||||
restbed::Http::async(req,
|
||||
[this, ok](const std::shared_ptr<restbed::Request>& /*req*/,
|
||||
const std::shared_ptr<restbed::Response>& reply) {
|
||||
auto code = reply->get_status_code();
|
||||
|
||||
if (code == 200) {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
reply->set_body(""); // Reset the body for the next fetch
|
||||
|
||||
try {
|
||||
std::string err;
|
||||
Json::Value json;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&body[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (not reader->parse(char_data, char_data + body.size(), &json, &err))
|
||||
*ok = false;
|
||||
} catch (...) {
|
||||
*ok = false;
|
||||
}
|
||||
} else {
|
||||
*ok = false;
|
||||
}
|
||||
}).wait();
|
||||
if (cb) {
|
||||
std::lock_guard<std::mutex> lock(lockCallbacks);
|
||||
callbacks_.emplace_back([=](){
|
||||
cb(*ok, {});
|
||||
});
|
||||
loopSignal_();
|
||||
}
|
||||
if (!ok) {
|
||||
// Connection failed, update connectivity
|
||||
opFailed();
|
||||
}
|
||||
*finished = true;
|
||||
});
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockOperations_);
|
||||
operations_.emplace_back(std::move(o));
|
||||
}
|
||||
}
|
||||
|
||||
NodeStats
|
||||
DhtProxyClient::getNodesStats(sa_family_t af) const
|
||||
{
|
||||
return af == AF_INET ? stats4_ : stats6_;
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::getProxyInfos()
|
||||
{
|
||||
DHT_LOG.DEBUG("Requesting proxy server node information");
|
||||
|
||||
if (ongoingStatusUpdate_.test_and_set())
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
|
||||
if (statusIpv4_ == NodeStatus::Disconnected)
|
||||
statusIpv4_ = NodeStatus::Connecting;
|
||||
if (statusIpv6_ == NodeStatus::Disconnected)
|
||||
statusIpv6_ = NodeStatus::Connecting;
|
||||
}
|
||||
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/");
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
|
||||
// Try to contact the proxy and set the status to connected when done.
|
||||
// will change the connectivity status
|
||||
statusThread_ = std::thread([this, req]{
|
||||
restbed::Http::async(req,
|
||||
[this](const std::shared_ptr<restbed::Request>&,
|
||||
const std::shared_ptr<restbed::Response>& reply) {
|
||||
auto code = reply->get_status_code();
|
||||
Json::Value proxyInfos;
|
||||
if (code == 200) {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
|
||||
std::string err;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
try {
|
||||
reader->parse(body.data(), body.data() + body.size(), &proxyInfos, &err);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
onProxyInfos(proxyInfos);
|
||||
ongoingStatusUpdate_.clear();
|
||||
});
|
||||
});
|
||||
statusThread_.detach();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
|
||||
|
||||
auto oldStatus = std::max(statusIpv4_, statusIpv6_);
|
||||
|
||||
try {
|
||||
myid = InfoHash(proxyInfos["node_id"].asString());
|
||||
|
||||
stats4_ = NodeStats(proxyInfos["ipv4"]);
|
||||
if (stats4_.good_nodes)
|
||||
statusIpv4_ = NodeStatus::Connected;
|
||||
else if (stats4_.dubious_nodes)
|
||||
statusIpv4_ = NodeStatus::Connecting;
|
||||
else
|
||||
statusIpv4_ = NodeStatus::Disconnected;
|
||||
|
||||
stats6_ = NodeStats(proxyInfos["ipv6"]);
|
||||
if (stats6_.good_nodes)
|
||||
statusIpv6_ = NodeStatus::Connected;
|
||||
else if (stats6_.dubious_nodes)
|
||||
statusIpv6_ = NodeStatus::Connecting;
|
||||
else
|
||||
statusIpv6_ = NodeStatus::Disconnected;
|
||||
|
||||
publicAddress_ = parsePublicAddress(proxyInfos["public_ip"]);
|
||||
} catch (...) {}
|
||||
|
||||
auto newStatus = std::max(statusIpv4_, statusIpv6_);
|
||||
|
||||
if (newStatus == NodeStatus::Connecting || newStatus == NodeStatus::Connected) {
|
||||
if (oldStatus == NodeStatus::Disconnected) {
|
||||
restartListeners();
|
||||
}
|
||||
scheduler.edit(nextProxyConfirmation, scheduler.time() + std::chrono::minutes(15));
|
||||
}
|
||||
else if (newStatus == NodeStatus::Disconnected) {
|
||||
scheduler.edit(nextProxyConfirmation, scheduler.time() + std::chrono::minutes(1));
|
||||
}
|
||||
loopSignal_();
|
||||
}
|
||||
|
||||
SockAddr
|
||||
DhtProxyClient::parsePublicAddress(const Json::Value& val)
|
||||
{
|
||||
auto public_ip = val.asString();
|
||||
auto endIp = public_ip.find_last_of(':');
|
||||
std::string service = public_ip.substr(endIp + 1);
|
||||
std::string address = public_ip.substr(0, endIp - 1);
|
||||
auto sa = SockAddr::resolve(address, service);
|
||||
if (sa.empty()) return {};
|
||||
return sa.front().getMappedIPv4();
|
||||
}
|
||||
|
||||
std::vector<SockAddr>
|
||||
DhtProxyClient::getPublicAddress(sa_family_t family)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
|
||||
if (not publicAddress_) return {};
|
||||
return publicAddress_.getFamily() == family ? std::vector<SockAddr>{publicAddress_} : std::vector<SockAddr>{};
|
||||
}
|
||||
|
||||
size_t
|
||||
DhtProxyClient::listen(const InfoHash& key, GetCallback cb, Value::Filter filter, Where where)
|
||||
{
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/" + key.toString());
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
req->set_method(deviceKey_.empty() ? "LISTEN" : "SUBSCRIBE");
|
||||
|
||||
Query query {{}, where};
|
||||
auto filterChain = filter.chain(query.where.getFilter());
|
||||
auto pushNotifToken = std::make_shared<unsigned>(0);
|
||||
|
||||
Listener l;
|
||||
++listener_token_;
|
||||
l.key = key.toString();
|
||||
l.token = listener_token_;
|
||||
l.req = req;
|
||||
l.cb = cb;
|
||||
l.pushNotifToken = pushNotifToken;
|
||||
l.filterChain = std::move(filterChain);
|
||||
l.thread = std::thread([=]()
|
||||
{
|
||||
auto settings = std::make_shared<restbed::Settings>();
|
||||
if (deviceKey_.empty()) {
|
||||
std::chrono::milliseconds timeout(std::numeric_limits<int>::max());
|
||||
settings->set_connection_timeout(timeout); // Avoid the client to close the socket after 5 seconds.
|
||||
}
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
else
|
||||
fillBodyToGetToken(req);
|
||||
#endif
|
||||
|
||||
struct State {
|
||||
std::atomic_bool ok {true};
|
||||
std::atomic_bool cancel {false};
|
||||
};
|
||||
auto state = std::make_shared<State>();
|
||||
restbed::Http::async(req,
|
||||
[this, filterChain, cb, pushNotifToken, state](const std::shared_ptr<restbed::Request>& req,
|
||||
const std::shared_ptr<restbed::Response>& reply) {
|
||||
auto code = reply->get_status_code();
|
||||
if (code == 200) {
|
||||
try {
|
||||
std::string err;
|
||||
Json::Value json;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (!deviceKey_.empty()) {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
if (state->cancel)
|
||||
return;
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
|
||||
auto* char_data = reinterpret_cast<const char*>(&body[0]);
|
||||
if (reader->parse(char_data, char_data + body.size(), &json, &err)) {
|
||||
if (!json.isMember("token")) return;
|
||||
*pushNotifToken = json["token"].asLargestUInt();
|
||||
} else {
|
||||
state->ok = false;
|
||||
}
|
||||
} else {
|
||||
while (restbed::Http::is_open(req) and not state->cancel) {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
if (state->cancel)
|
||||
break;
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
reply->set_body(""); // Reset the body for the next fetch
|
||||
|
||||
auto* char_data = reinterpret_cast<const char*>(&body[0]);
|
||||
if (reader->parse(char_data, char_data + body.size(), &json, &err)) {
|
||||
auto value = std::make_shared<Value>(json);
|
||||
if ((not filterChain or filterChain(*value)) && cb) {
|
||||
std::lock_guard<std::mutex> lock(lockCallbacks);
|
||||
callbacks_.emplace_back([cb, value, state]() {
|
||||
if (not state->cancel and not cb({value})) {
|
||||
state->cancel = true;
|
||||
}
|
||||
});
|
||||
loopSignal_();
|
||||
}
|
||||
} else {
|
||||
state->ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::runtime_error&) {
|
||||
state->ok = false;
|
||||
}
|
||||
} else {
|
||||
state->ok = false;
|
||||
}
|
||||
}, settings).get();
|
||||
if (not state->ok) {
|
||||
opFailed();
|
||||
}
|
||||
}
|
||||
);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
listeners_.emplace_back(std::move(l));
|
||||
}
|
||||
return listener_token_;
|
||||
}
|
||||
|
||||
bool
|
||||
DhtProxyClient::cancelListen(const InfoHash&, size_t token)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
for (auto it = listeners_.begin(); it != listeners_.end(); ++it) {
|
||||
auto& listener = *it;
|
||||
if (listener.token == token) {
|
||||
if (!deviceKey_.empty()) {
|
||||
// First, be sure to have a token
|
||||
if (listener.thread.joinable()) {
|
||||
listener.thread.join();
|
||||
}
|
||||
// UNSUBSCRIBE
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/" + listener.key);
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
req->set_method("UNSUBSCRIBE");
|
||||
restbed::Http::async(req,
|
||||
[](const std::shared_ptr<restbed::Request>&,
|
||||
const std::shared_ptr<restbed::Response>&){}
|
||||
);
|
||||
// And remove
|
||||
listeners_.erase(it);
|
||||
return true;
|
||||
} else {
|
||||
// Just stop the request
|
||||
if (listener.thread.joinable()) {
|
||||
// Close connection to stop listener?
|
||||
if (listener.req)
|
||||
restbed::Http::close(listener.req);
|
||||
listener.thread.join();
|
||||
listeners_.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::opFailed()
|
||||
{
|
||||
DHT_LOG.ERR("Proxy request failed");
|
||||
{
|
||||
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
|
||||
statusIpv4_ = NodeStatus::Disconnected;
|
||||
statusIpv6_ = NodeStatus::Disconnected;
|
||||
}
|
||||
getConnectivityStatus();
|
||||
loopSignal_();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::getConnectivityStatus()
|
||||
{
|
||||
getProxyInfos();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::restartListeners()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
for (auto& listener: listeners_) {
|
||||
if (listener.thread.joinable())
|
||||
listener.thread.join();
|
||||
// Redo listen
|
||||
auto filterChain = listener.filterChain;
|
||||
auto cb = listener.cb;
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/" + listener.key);
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
req->set_method("LISTEN");
|
||||
listener.req = req;
|
||||
listener.thread = std::thread([this, filterChain, cb, req]()
|
||||
{
|
||||
auto settings = std::make_shared<restbed::Settings>();
|
||||
std::chrono::milliseconds timeout(std::numeric_limits<int>::max());
|
||||
settings->set_connection_timeout(timeout); // Avoid the client to close the socket after 5 seconds.
|
||||
|
||||
auto ok = std::make_shared<std::atomic_bool>(true);
|
||||
restbed::Http::async(req,
|
||||
[this, filterChain, cb, ok](const std::shared_ptr<restbed::Request>& req,
|
||||
const std::shared_ptr<restbed::Response>& reply) {
|
||||
auto code = reply->get_status_code();
|
||||
|
||||
if (code == 200) {
|
||||
try {
|
||||
while (restbed::Http::is_open(req)) {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
reply->set_body(""); // Reset the body for the next fetch
|
||||
|
||||
Json::Value json;
|
||||
std::string err;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&body[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (reader->parse(char_data, char_data + body.size(), &json, &err)) {
|
||||
auto value = std::make_shared<Value>(json);
|
||||
if ((not filterChain or filterChain(*value)) && cb) {
|
||||
auto okCb = std::make_shared<std::promise<bool>>();
|
||||
auto futureCb = okCb->get_future();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockCallbacks);
|
||||
callbacks_.emplace_back([cb, value, okCb](){
|
||||
okCb->set_value(cb({value}));
|
||||
});
|
||||
loopSignal_();
|
||||
}
|
||||
futureCb.wait();
|
||||
if (!futureCb.get()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::runtime_error&) {
|
||||
// NOTE: Http::close() can occurs here. Ignore this.
|
||||
}
|
||||
} else {
|
||||
*ok = false;
|
||||
}
|
||||
}, settings).get();
|
||||
if (!ok) opFailed();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
void
|
||||
DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string>& notification)
|
||||
{
|
||||
try {
|
||||
auto token = std::stoul(notification.at("token"));
|
||||
for (const auto& listener: listeners_) {
|
||||
if (*(listener.pushNotifToken) != token)
|
||||
continue;
|
||||
if (notification.find("timeout") == notification.cend()) {
|
||||
// Wake up daemon and get values
|
||||
get(InfoHash(listener.key), listener.cb, {}, listener.filterChain);
|
||||
} else {
|
||||
// A timeout has occured, we need to relaunch the listener
|
||||
resubscribe(token);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::resubscribe(const unsigned token)
|
||||
{
|
||||
if (deviceKey_.empty()) return;
|
||||
for (auto& listener: listeners_) {
|
||||
if (*(listener.pushNotifToken) == token) {
|
||||
// Subscribe
|
||||
restbed::Uri uri(HTTP_PROTO + serverHost_ + "/" + listener.key);
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
req->set_method("SUBSCRIBE");
|
||||
|
||||
auto pushNotifToken = std::make_shared<unsigned>(0);
|
||||
|
||||
if (listener.thread.joinable())
|
||||
listener.thread.join();
|
||||
listener.req = req;
|
||||
listener.pushNotifToken = pushNotifToken;
|
||||
listener.thread = std::thread([=]()
|
||||
{
|
||||
fillBodyToGetToken(req);
|
||||
auto settings = std::make_shared<restbed::Settings>();
|
||||
auto ok = std::make_shared<std::atomic_bool>(true);
|
||||
restbed::Http::async(req,
|
||||
[this, pushNotifToken, ok](const std::shared_ptr<restbed::Request>&,
|
||||
const std::shared_ptr<restbed::Response>& reply) {
|
||||
auto code = reply->get_status_code();
|
||||
if (code == 200) {
|
||||
try {
|
||||
restbed::Http::fetch("\n", reply);
|
||||
std::string body;
|
||||
reply->get_body(body);
|
||||
|
||||
std::string err;
|
||||
Json::Value json;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&body[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (reader->parse(char_data, char_data + body.size(), &json, &err)) {
|
||||
if (!json.isMember("token")) return;
|
||||
*pushNotifToken = json["token"].asLargestUInt();
|
||||
}
|
||||
} catch (std::runtime_error&) {
|
||||
// NOTE: Http::close() can occurs here. Ignore this.
|
||||
}
|
||||
} else {
|
||||
*ok = false;
|
||||
}
|
||||
}, settings).get();
|
||||
if (!ok) opFailed();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClient::fillBodyToGetToken(std::shared_ptr<restbed::Request> req)
|
||||
{
|
||||
// Fill body with
|
||||
// {
|
||||
// "key":"device_key",
|
||||
// "callback_id": xxx
|
||||
// }
|
||||
Json::Value body;
|
||||
body["key"] = deviceKey_;
|
||||
body["client_id"] = pushClientId_;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockCallback_);
|
||||
callbackId_ += 1;
|
||||
body["callback_id"] = callbackId_;
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
body["platform"] = "android";
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
body["platform"] = "apple";
|
||||
#endif
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto content = Json::writeString(wbuilder, body) + "\n";
|
||||
std::replace(content.begin(), content.end(), '\n', ' ');
|
||||
req->set_body(content);
|
||||
req->set_header("Content-Length", std::to_string(content.size()));
|
||||
}
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
} // namespace dht
|
||||
|
||||
#endif // OPENDHT_PROXY_CLIENT
|
||||
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* Copyright (C) 2017-2018 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
#include "dht_proxy_server.h"
|
||||
|
||||
#include "default_types.h"
|
||||
#include "dhtrunner.h"
|
||||
#include "msgpack.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <json/json.h>
|
||||
#include <limits>
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
constexpr int const TIMEOUT {6 * 60 * 60}; // in seconds (so six hours)
|
||||
constexpr const char* const HTTP_PROTO {"http://"}; // TODO, https for prod
|
||||
#endif //OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace dht {
|
||||
|
||||
DhtProxyServer::DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port , const std::string& pushServer)
|
||||
: dht_(dht) , pushServer_(pushServer)
|
||||
{
|
||||
// NOTE in c++14, use make_unique
|
||||
service_ = std::unique_ptr<restbed::Service>(new restbed::Service());
|
||||
|
||||
#if !OPENDHT_PUSH_NOTIFICATIONS
|
||||
if (not pushServer.empty())
|
||||
std::cerr << "Push server defined but built OpenDHT built without push notification support" << std::endl;
|
||||
#endif
|
||||
|
||||
server_thread = std::thread([this, port]() {
|
||||
// Create endpoints
|
||||
auto resource = std::make_shared<restbed::Resource>();
|
||||
resource->set_path("/");
|
||||
resource->set_method_handler("GET", std::bind(&DhtProxyServer::getNodeInfo, this, _1));
|
||||
service_->publish(resource);
|
||||
resource = std::make_shared<restbed::Resource>();
|
||||
resource->set_path("/{hash: .*}");
|
||||
resource->set_method_handler("GET", std::bind(&DhtProxyServer::get, this, _1));
|
||||
resource->set_method_handler("LISTEN", std::bind(&DhtProxyServer::listen, this, _1));
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
resource->set_method_handler("SUBSCRIBE", std::bind(&DhtProxyServer::subscribe, this, _1));
|
||||
resource->set_method_handler("UNSUBSCRIBE", std::bind(&DhtProxyServer::unsubscribe, this, _1));
|
||||
#endif //OPENDHT_PUSH_NOTIFICATIONS
|
||||
resource->set_method_handler("POST", std::bind(&DhtProxyServer::put, this, _1));
|
||||
#if OPENDHT_PROXY_SERVER_IDENTITY
|
||||
resource->set_method_handler("SIGN", std::bind(&DhtProxyServer::putSigned, this, _1));
|
||||
resource->set_method_handler("ENCRYPT", std::bind(&DhtProxyServer::putEncrypted, this, _1));
|
||||
#endif // OPENDHT_PROXY_SERVER_IDENTITY
|
||||
resource->set_method_handler("OPTIONS", std::bind(&DhtProxyServer::handleOptionsMethod, this, _1));
|
||||
service_->publish(resource);
|
||||
resource = std::make_shared<restbed::Resource>();
|
||||
resource->set_path("/{hash: .*}/{value: .*}");
|
||||
resource->set_method_handler("GET", std::bind(&DhtProxyServer::getFiltered, this, _1));
|
||||
service_->publish(resource);
|
||||
|
||||
// Start server
|
||||
auto settings = std::make_shared<restbed::Settings>();
|
||||
settings->set_default_header("Content-Type", "application/json");
|
||||
settings->set_default_header("Connection", "keep-alive");
|
||||
settings->set_default_header("Access-Control-Allow-Origin", "*");
|
||||
std::chrono::milliseconds timeout(std::numeric_limits<int>::max());
|
||||
settings->set_connection_timeout(timeout); // there is a timeout, but really huge
|
||||
settings->set_port(port);
|
||||
auto maxThreads = std::thread::hardware_concurrency() - 1;
|
||||
settings->set_worker_limit(maxThreads > 1 ? maxThreads : 1);
|
||||
try {
|
||||
service_->start(settings);
|
||||
} catch(std::system_error& e) {
|
||||
std::cerr << "Error running server on port " << port << ": " << e.what() << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
listenThread_ = std::thread([this]() {
|
||||
while (!service_->is_up() && !stopListeners) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
while (service_->is_up() && !stopListeners) {
|
||||
removeClosedListeners();
|
||||
// add listener from push notifs
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
handlePushListeners();
|
||||
#endif //OPENDHT_PUSH_NOTIFICATIONS
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
// Remove last listeners
|
||||
removeClosedListeners(false);
|
||||
});
|
||||
|
||||
dht->forwardAllMessages(true);
|
||||
}
|
||||
|
||||
DhtProxyServer::~DhtProxyServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::stop()
|
||||
{
|
||||
service_->stop();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
auto listener = currentListeners_.begin();
|
||||
while (listener != currentListeners_.end()) {
|
||||
listener->session->close();
|
||||
++ listener;
|
||||
}
|
||||
}
|
||||
stopListeners = true;
|
||||
// listenThreads_ will stop because there is no more sessions
|
||||
if (listenThread_.joinable())
|
||||
listenThread_.join();
|
||||
if (server_thread.joinable())
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::getNodeInfo(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b*/)
|
||||
{
|
||||
if (dht_) {
|
||||
Json::Value result;
|
||||
auto id = dht_->getId();
|
||||
if (id)
|
||||
result["id"] = id.toString();
|
||||
result["node_id"] = dht_->getNodeId().toString();
|
||||
result["ipv4"] = dht_->getNodesStats(AF_INET).toJson();
|
||||
result["ipv6"] = dht_->getNodesStats(AF_INET6).toJson();
|
||||
result["public_ip"] = s->get_origin(); // [ipv6:ipv4]:port or ipv4:port
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto output = Json::writeString(wbuilder, result) + "\n";
|
||||
s->close(restbed::OK, output);
|
||||
}
|
||||
else
|
||||
s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::get(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
|
||||
{
|
||||
if (dht_) {
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash) {
|
||||
infoHash = InfoHash::get(hash);
|
||||
}
|
||||
s->yield(restbed::OK, "", [=]( const std::shared_ptr< restbed::Session > s) {
|
||||
auto cacheSession = std::weak_ptr<restbed::Session>(s);
|
||||
|
||||
dht_->get(infoHash, [cacheSession](std::shared_ptr<Value> value) {
|
||||
auto s = cacheSession.lock();
|
||||
if (!s) return false;
|
||||
// Send values as soon as we get them
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto output = Json::writeString(wbuilder, value->toJson()) + "\n";
|
||||
s->yield(output, [](const std::shared_ptr<restbed::Session> /*session*/){ });
|
||||
return true;
|
||||
}, [s](bool /*ok* */) {
|
||||
// Communication is finished
|
||||
s->close();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::listen(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash)
|
||||
infoHash = InfoHash::get(hash);
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
|
||||
{
|
||||
if (dht_) {
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash) {
|
||||
infoHash = InfoHash::get(hash);
|
||||
}
|
||||
s->yield(restbed::OK);
|
||||
// Handle client deconnection
|
||||
// NOTE: for now, there is no handler, so we test the session in a thread
|
||||
// will be the case in restbed 5.0
|
||||
SessionToHashToken listener;
|
||||
listener.session = session;
|
||||
listener.hash = infoHash;
|
||||
// cache the session to avoid an incrementation of the shared_ptr's counter
|
||||
// else, the session->close() will not close the socket.
|
||||
auto cacheSession = std::weak_ptr<restbed::Session>(s);
|
||||
listener.token = dht_->listen(infoHash, [cacheSession](std::shared_ptr<Value> value) {
|
||||
auto s = cacheSession.lock();
|
||||
if (!s) return false;
|
||||
// Send values as soon as we get them
|
||||
if (!s->is_closed()) {
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto output = Json::writeString(wbuilder, value->toJson()) + "\n";
|
||||
s->yield(output, [](const std::shared_ptr<restbed::Session>){ });
|
||||
}
|
||||
return !s->is_closed();
|
||||
});
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
currentListeners_.emplace_back(std::move(listener));
|
||||
}
|
||||
} else {
|
||||
session->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
void
|
||||
DhtProxyServer::subscribe(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash)
|
||||
infoHash = InfoHash::get(hash);
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
|
||||
{
|
||||
try {
|
||||
restbed::Bytes buf(b);
|
||||
std::string strJson(buf.begin(), buf.end());
|
||||
|
||||
std::string err;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&strJson[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (!reader->parse(char_data, char_data + strJson.size(), &root, &err)) {
|
||||
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
|
||||
return;
|
||||
}
|
||||
auto pushToken = root["key"].asString();
|
||||
if (pushToken.empty()) return;
|
||||
auto callbackId = root.isMember("callback_id") ? root["callback_id"].asLargestUInt() : 0;
|
||||
auto platform = root["platform"].asString();
|
||||
auto isAndroid = platform == "android";
|
||||
auto clientId = root.isMember("client_id") ? root["client_id"].asString() : std::string();
|
||||
|
||||
auto token = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
// Check if listener is already present and refresh timeout if launched
|
||||
for(auto& listener: pushListeners_) {
|
||||
if (listener.pushToken == pushToken && listener.hash == infoHash
|
||||
&& listener.callbackId == callbackId) {
|
||||
if (listener.started)
|
||||
listener.deadline = std::chrono::steady_clock::now()
|
||||
+ std::chrono::seconds(TIMEOUT);
|
||||
s->close(restbed::OK, "{\"token\": " + std::to_string(listener.token) + "}\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// The listener is not found, so add it.
|
||||
++tokenPushNotif_;
|
||||
token = tokenPushNotif_;
|
||||
PushListener listener;
|
||||
listener.pushToken = pushToken;
|
||||
listener.hash = std::move(infoHash);
|
||||
listener.token = token;
|
||||
listener.started = false;
|
||||
listener.callbackId = callbackId;
|
||||
listener.clientId = clientId;
|
||||
listener.isAndroid = isAndroid;
|
||||
pushListeners_.emplace_back(std::move(listener));
|
||||
}
|
||||
s->close(restbed::OK, "{\"token\": " + std::to_string(token) + "}\n");
|
||||
} catch (...) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::unsubscribe(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash)
|
||||
infoHash = InfoHash::get(hash);
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
|
||||
{
|
||||
try {
|
||||
restbed::Bytes buf(b);
|
||||
std::string strJson(buf.begin(), buf.end());
|
||||
|
||||
std::string err;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&strJson[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (!reader->parse(char_data, char_data + strJson.size(), &root, &err)) {
|
||||
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
|
||||
return;
|
||||
}
|
||||
auto pushToken = root["key"].asString();
|
||||
if (pushToken.empty()) return;
|
||||
auto token = root["token"].asLargestUInt();
|
||||
if (token == 0) return;
|
||||
auto callbackId = root.isMember("callback_id") ? root["callback_id"].asLargestUInt() : 0;
|
||||
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
// Check if listener is already present and refresh timeout if launched
|
||||
auto listener = pushListeners_.begin();
|
||||
while (listener != pushListeners_.end()) {
|
||||
if (listener->pushToken == pushToken && listener->token == token
|
||||
&& listener->hash == infoHash && listener->callbackId == callbackId) {
|
||||
if (dht_ && listener->started)
|
||||
dht_->cancelListen(listener->hash, std::move(listener->internalToken.get()));
|
||||
listener = pushListeners_.erase(listener);
|
||||
} else {
|
||||
++listener;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::sendPushNotification(const std::string& token, const Json::Value& json, bool isAndroid) const
|
||||
{
|
||||
restbed::Uri uri(HTTP_PROTO + pushServer_ + "/api/push");
|
||||
auto req = std::make_shared<restbed::Request>(uri);
|
||||
req->set_method("POST");
|
||||
|
||||
// NOTE: see https://github.com/appleboy/gorush
|
||||
Json::Value notification(Json::objectValue);
|
||||
Json::Value tokens(Json::arrayValue);
|
||||
tokens[0] = token;
|
||||
notification["tokens"] = tokens;
|
||||
notification["platform"] = isAndroid ? 2 : 1;
|
||||
notification["data"] = json;
|
||||
|
||||
Json::Value notifications(Json::arrayValue);
|
||||
notifications[0] = notification;
|
||||
|
||||
Json::Value content;
|
||||
content["notifications"] = notifications;
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto valueStr = Json::writeString(wbuilder, content);
|
||||
|
||||
req->set_header("Content-Type", "application/json");
|
||||
req->set_header("Accept", "*/*");
|
||||
req->set_header("Host", pushServer_);
|
||||
req->set_header("Content-Length", std::to_string(valueStr.length()));
|
||||
req->set_body(valueStr);
|
||||
// Send request.
|
||||
restbed::Http::async(req, {});
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::handlePushListeners()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
auto pushListener = pushListeners_.begin();
|
||||
while (pushListener != pushListeners_.end()) {
|
||||
if (dht_ && !pushListener->started) {
|
||||
// Try to start unstarted listeners
|
||||
auto key = pushListener->pushToken;
|
||||
auto token = pushListener->token;
|
||||
auto callbackId = pushListener->callbackId;
|
||||
auto isAndroid = pushListener->isAndroid;
|
||||
auto clientId = pushListener->clientId;
|
||||
pushListener->internalToken = dht_->listen(pushListener->hash,
|
||||
[this, key, callbackId, token, isAndroid, clientId](std::shared_ptr<Value> /*value*/) {
|
||||
// Build message content.
|
||||
Json::Value json;
|
||||
if (callbackId > 0) {
|
||||
json["callback_id"] = callbackId;
|
||||
}
|
||||
json["to"] = clientId;
|
||||
json["token"] = token;
|
||||
sendPushNotification(key, json, isAndroid);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
pushListener->deadline = std::chrono::steady_clock::now() + std::chrono::seconds(TIMEOUT);
|
||||
pushListener->started = true;
|
||||
pushListener++;
|
||||
} else if (dht_ && pushListener->started && pushListener->deadline < std::chrono::steady_clock::now()) {
|
||||
// Cancel listen if deadline has been reached
|
||||
dht_->cancelListen(pushListener->hash, std::move(pushListener->internalToken.get()));
|
||||
// Send a push notification to inform the client that this listen has timeout
|
||||
Json::Value json;
|
||||
json["timeout"] = pushListener->hash.toString();
|
||||
if (pushListener->callbackId > 0) {
|
||||
json["callback_id"] = pushListener->callbackId;
|
||||
}
|
||||
json["to"] = pushListener->clientId;
|
||||
json["token"] = pushListener->token;
|
||||
sendPushNotification(pushListener->pushToken, json, pushListener->isAndroid);
|
||||
pushListener = pushListeners_.erase(pushListener);
|
||||
} else {
|
||||
pushListener++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //OPENDHT_PUSH_NOTIFICATIONS
|
||||
|
||||
void
|
||||
DhtProxyServer::put(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash)
|
||||
infoHash = InfoHash::get(hash);
|
||||
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
|
||||
{
|
||||
if (dht_) {
|
||||
if(b.empty()) {
|
||||
std::string response("{\"err\":\"Missing parameters\"}");
|
||||
s->close(restbed::BAD_REQUEST, response);
|
||||
} else {
|
||||
restbed::Bytes buf(b);
|
||||
std::string strJson(buf.begin(), buf.end());
|
||||
|
||||
std::string err;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&strJson[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (reader->parse(char_data, char_data + strJson.size(), &root, &err)) {
|
||||
// Build the Value from json
|
||||
auto value = std::make_shared<Value>(root);
|
||||
auto permanent = root.isMember("permanent") ? root["permanent"].asBool() : false;
|
||||
|
||||
dht_->put(infoHash, value, [s, value](bool ok) {
|
||||
if (ok) {
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
if (s->is_open())
|
||||
s->close(restbed::OK, Json::writeString(wbuilder, value->toJson()) + "\n");
|
||||
} else {
|
||||
if (s->is_open())
|
||||
s->close(restbed::BAD_GATEWAY, "{\"err\":\"put failed\"}");
|
||||
}
|
||||
}, time_point::max(), permanent);
|
||||
} else {
|
||||
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#if OPENDHT_PROXY_SERVER_IDENTITY
|
||||
void
|
||||
DhtProxyServer::putSigned(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash)
|
||||
infoHash = InfoHash::get(hash);
|
||||
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
|
||||
{
|
||||
if (dht_) {
|
||||
if(b.empty()) {
|
||||
std::string response("{\"err\":\"Missing parameters\"}");
|
||||
s->close(restbed::BAD_REQUEST, response);
|
||||
} else {
|
||||
restbed::Bytes buf(b);
|
||||
std::string strJson(buf.begin(), buf.end());
|
||||
|
||||
std::string err;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&strJson[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
if (reader->parse(char_data, char_data + strJson.size(), &root, &err)) {
|
||||
auto value = std::make_shared<Value>(root);
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto output = Json::writeString(wbuilder, value->toJson()) + "\n";
|
||||
dht_->putSigned(infoHash, value);
|
||||
s->close(restbed::OK, output);
|
||||
} else {
|
||||
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::putEncrypted(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
InfoHash key(hash);
|
||||
if (!key)
|
||||
key = InfoHash::get(hash);
|
||||
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
|
||||
{
|
||||
if (dht_) {
|
||||
if(b.empty()) {
|
||||
std::string response("{\"err\":\"Missing parameters\"}");
|
||||
s->close(restbed::BAD_REQUEST, response);
|
||||
} else {
|
||||
restbed::Bytes buf(b);
|
||||
std::string strJson(buf.begin(), buf.end());
|
||||
|
||||
std::string err;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
auto* char_data = reinterpret_cast<const char*>(&strJson[0]);
|
||||
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
|
||||
bool parsingSuccessful = reader->parse(char_data, char_data + strJson.size(), &root, &err);
|
||||
InfoHash to(root["to"].asString());
|
||||
if (parsingSuccessful && to) {
|
||||
auto value = std::make_shared<Value>(root);
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto output = Json::writeString(wbuilder, value->toJson()) + "\n";
|
||||
dht_->putEncrypted(key, to, value);
|
||||
s->close(restbed::OK, output);
|
||||
} else {
|
||||
if(!parsingSuccessful)
|
||||
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
|
||||
else
|
||||
s->close(restbed::BAD_REQUEST, "{\"err\":\"No destination found\"}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
#endif // OPENDHT_PROXY_SERVER_IDENTITY
|
||||
|
||||
void
|
||||
DhtProxyServer::handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
#if OPENDHT_PROXY_SERVER_IDENTITY
|
||||
const auto allowed = "OPTIONS, GET, POST, LISTEN, SIGN, ENCRYPT";
|
||||
#else
|
||||
const auto allowed = "OPTIONS, GET, POST, LISTEN";
|
||||
#endif //OPENDHT_PROXY_SERVER_IDENTITY
|
||||
session->close(restbed::OK, {{"Access-Control-Allow-Methods", allowed},
|
||||
{"Access-Control-Allow-Headers", "content-type"},
|
||||
{"Access-Control-Max-Age", "86400"}});
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::getFiltered(const std::shared_ptr<restbed::Session>& session) const
|
||||
{
|
||||
const auto request = session->get_request();
|
||||
int content_length = std::stoi(request->get_header("Content-Length", "0"));
|
||||
auto hash = request->get_path_parameter("hash");
|
||||
auto value = request->get_path_parameter("value");
|
||||
session->fetch(content_length,
|
||||
[=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
|
||||
{
|
||||
if (dht_) {
|
||||
InfoHash infoHash(hash);
|
||||
if (!infoHash) {
|
||||
infoHash = InfoHash::get(hash);
|
||||
}
|
||||
s->yield(restbed::OK, "", [=]( const std::shared_ptr< restbed::Session > s) {
|
||||
dht_->get(infoHash, [s](std::shared_ptr<Value> v) {
|
||||
// Send values as soon as we get them
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder["commentStyle"] = "None";
|
||||
wbuilder["indentation"] = "";
|
||||
auto output = Json::writeString(wbuilder, v->toJson()) + "\n";
|
||||
s->yield(output, [](const std::shared_ptr<restbed::Session> /*session*/){ });
|
||||
return true;
|
||||
}, [s](bool /*ok* */) {
|
||||
// Communication is finished
|
||||
s->close();
|
||||
}, {}, value);
|
||||
});
|
||||
} else {
|
||||
s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyServer::removeClosedListeners(bool testSession)
|
||||
{
|
||||
// clean useless listeners
|
||||
std::lock_guard<std::mutex> lock(lockListener_);
|
||||
auto listener = currentListeners_.begin();
|
||||
while (listener != currentListeners_.end()) {
|
||||
auto cancel = testSession ? dht_ && listener->session->is_closed() : static_cast<bool>(dht_);
|
||||
if (cancel) {
|
||||
dht_->cancelListen(listener->hash, std::move(listener->token.get()));
|
||||
// Remove listener if unused
|
||||
listener = currentListeners_.erase(listener);
|
||||
} else {
|
||||
++listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
+297
-131
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,6 +21,10 @@
|
||||
#include "dhtrunner.h"
|
||||
#include "securedht.h"
|
||||
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
#include "dht_proxy_client.h"
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#else
|
||||
@@ -39,6 +44,9 @@ namespace dht {
|
||||
constexpr std::chrono::seconds DhtRunner::BOOTSTRAP_PERIOD;
|
||||
|
||||
DhtRunner::DhtRunner() : dht_()
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
, dht_via_proxy_()
|
||||
#endif //OPENDHT_PROXY_CLIENT
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsd;
|
||||
@@ -58,41 +66,51 @@ DhtRunner::~DhtRunner()
|
||||
void
|
||||
DhtRunner::run(in_port_t port, DhtRunner::Config config)
|
||||
{
|
||||
sockaddr_in sin4;
|
||||
std::fill_n((uint8_t*)&sin4, sizeof(sin4), 0);
|
||||
sin4.sin_family = AF_INET;
|
||||
sin4.sin_port = htons(port);
|
||||
sockaddr_in6 sin6;
|
||||
std::fill_n((uint8_t*)&sin6, sizeof(sin6), 0);
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_port = htons(port);
|
||||
run(&sin4, &sin6, config);
|
||||
SockAddr sin4;
|
||||
sin4.setFamily(AF_INET);
|
||||
sin4.setPort(port);
|
||||
SockAddr sin6;
|
||||
sin6.setFamily(AF_INET6);
|
||||
sin6.setPort(port);
|
||||
run(sin4, sin6, config);
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::run(const char* ip4, const char* ip6, const char* service, DhtRunner::Config config)
|
||||
{
|
||||
auto res4 = getAddrInfo(ip4, service);
|
||||
auto res6 = getAddrInfo(ip6, service);
|
||||
run(res4.empty() ? nullptr : (sockaddr_in*) &res4.front().first,
|
||||
res6.empty() ? nullptr : (sockaddr_in6*)&res6.front().first, config);
|
||||
auto res4 = SockAddr::resolve(ip4, service);
|
||||
auto res6 = SockAddr::resolve(ip6, service);
|
||||
run(res4.empty() ? SockAddr() : res4.front(),
|
||||
res6.empty() ? SockAddr() : res6.front(), config);
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::run(const sockaddr_in* local4, const sockaddr_in6* local6, DhtRunner::Config config)
|
||||
DhtRunner::run(const SockAddr& local4, const SockAddr& local6, DhtRunner::Config config)
|
||||
{
|
||||
if (running)
|
||||
return;
|
||||
if (rcv_thread.joinable())
|
||||
rcv_thread.join();
|
||||
startNetwork(local4, local6);
|
||||
|
||||
auto dht = std::unique_ptr<DhtInterface>(new Dht(s4, s6, SecureDht::getConfig(config.dht_config)));
|
||||
dht_ = std::unique_ptr<SecureDht>(new SecureDht(std::move(dht), config.dht_config));
|
||||
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
config_ = config;
|
||||
#endif
|
||||
enableProxy(not config.proxy_server.empty());
|
||||
|
||||
running = true;
|
||||
doRun(local4, local6, config.dht_config);
|
||||
if (not config.threaded)
|
||||
return;
|
||||
dht_thread = std::thread([this]() {
|
||||
dht_thread = std::thread([this, local4, local6]() {
|
||||
while (running) {
|
||||
std::unique_lock<std::mutex> lk(dht_mtx);
|
||||
auto wakeup = loop_();
|
||||
time_point wakeup;
|
||||
try {
|
||||
wakeup = loop_();
|
||||
} catch (const dht::SocketException& e) {
|
||||
startNetwork(local4, local6);
|
||||
}
|
||||
cv.wait_until(lk, wakeup, [this]() {
|
||||
if (not running)
|
||||
return true;
|
||||
@@ -117,6 +135,10 @@ DhtRunner::run(const sockaddr_in* local4, const sockaddr_in6* local6, DhtRunner:
|
||||
|
||||
void
|
||||
DhtRunner::shutdown(ShutdownCallback cb) {
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->shutdown(cb);
|
||||
#endif
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops_prio.emplace([=](SecureDht& dht) mutable {
|
||||
dht.shutdown(cb);
|
||||
@@ -127,15 +149,16 @@ DhtRunner::shutdown(ShutdownCallback cb) {
|
||||
void
|
||||
DhtRunner::join()
|
||||
{
|
||||
running_network = false;
|
||||
running = false;
|
||||
cv.notify_all();
|
||||
bootstrap_cv.notify_all();
|
||||
if (dht_thread.joinable())
|
||||
dht_thread.join();
|
||||
if (rcv_thread.joinable())
|
||||
rcv_thread.join();
|
||||
if (bootstrap_thread.joinable())
|
||||
bootstrap_thread.join();
|
||||
if (rcv_thread.joinable())
|
||||
rcv_thread.join();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
@@ -144,11 +167,9 @@ DhtRunner::join()
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_.reset();
|
||||
resetDht();
|
||||
status4 = NodeStatus::Disconnected;
|
||||
status6 = NodeStatus::Disconnected;
|
||||
bound4 = {};
|
||||
bound6 = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,23 +177,23 @@ void
|
||||
DhtRunner::dumpTables() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_->dumpTables();
|
||||
activeDht()->dumpTables();
|
||||
}
|
||||
|
||||
InfoHash
|
||||
DhtRunner::getId() const
|
||||
{
|
||||
if (!dht_)
|
||||
if (!activeDht())
|
||||
return {};
|
||||
return dht_->getId();
|
||||
return activeDht()->getId();
|
||||
}
|
||||
|
||||
InfoHash
|
||||
DhtRunner::getNodeId() const
|
||||
{
|
||||
if (!dht_)
|
||||
if (!activeDht())
|
||||
return {};
|
||||
return dht_->getNodeId();
|
||||
return activeDht()->getNodeId();
|
||||
}
|
||||
|
||||
|
||||
@@ -211,19 +232,30 @@ DhtRunner::exportValues() const {
|
||||
void
|
||||
DhtRunner::setLoggers(LogMethod error, LogMethod warn, LogMethod debug) {
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_->setLoggers(std::forward<LogMethod>(error), std::forward<LogMethod>(warn), std::forward<LogMethod>(debug));
|
||||
if (dht_)
|
||||
dht_->setLoggers(std::forward<LogMethod>(error), std::forward<LogMethod>(warn), std::forward<LogMethod>(debug));
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->setLoggers(std::forward<LogMethod>(error), std::forward<LogMethod>(warn), std::forward<LogMethod>(debug));
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::setLogFilter(const InfoHash& f) {
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_->setLogFilter(f);
|
||||
activeDht()->setLogFilter(f);
|
||||
if (dht_)
|
||||
dht_->setLogFilter(f);
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->setLogFilter(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::registerType(const ValueType& type) {
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_->registerType(type);
|
||||
activeDht()->registerType(type);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -236,7 +268,7 @@ unsigned
|
||||
DhtRunner::getNodesStats(sa_family_t af, unsigned *good_return, unsigned *dubious_return, unsigned *cached_return, unsigned *incoming_return) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
const auto stats = dht_->getNodesStats(af);
|
||||
const auto stats = activeDht()->getNodesStats(af);
|
||||
if (good_return)
|
||||
*good_return = stats.good_nodes;
|
||||
if (dubious_return)
|
||||
@@ -252,76 +284,82 @@ NodeStats
|
||||
DhtRunner::getNodesStats(sa_family_t af) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getNodesStats(af);
|
||||
return activeDht()->getNodesStats(af);
|
||||
}
|
||||
|
||||
std::vector<unsigned>
|
||||
DhtRunner::getNodeMessageStats(bool in) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getNodeMessageStats(in);
|
||||
return activeDht()->getNodeMessageStats(in);
|
||||
}
|
||||
|
||||
std::string
|
||||
DhtRunner::getStorageLog() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getStorageLog();
|
||||
return activeDht()->getStorageLog();
|
||||
}
|
||||
std::string
|
||||
DhtRunner::getStorageLog(const InfoHash& f) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getStorageLog(f);
|
||||
return activeDht()->getStorageLog(f);
|
||||
}
|
||||
std::string
|
||||
DhtRunner::getRoutingTablesLog(sa_family_t af) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getRoutingTablesLog(af);
|
||||
return activeDht()->getRoutingTablesLog(af);
|
||||
}
|
||||
std::string
|
||||
DhtRunner::getSearchesLog(sa_family_t af) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getSearchesLog(af);
|
||||
return activeDht()->getSearchesLog(af);
|
||||
}
|
||||
std::string
|
||||
DhtRunner::getSearchLog(const InfoHash& f, sa_family_t af) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getSearchLog(f, af);
|
||||
return activeDht()->getSearchLog(f, af);
|
||||
}
|
||||
std::vector<SockAddr>
|
||||
DhtRunner::getPublicAddress(sa_family_t af)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
return dht_->getPublicAddress(af);
|
||||
return activeDht()->getPublicAddress(af);
|
||||
}
|
||||
std::vector<std::string>
|
||||
DhtRunner::getPublicAddressStr(sa_family_t af)
|
||||
{
|
||||
auto addrs = getPublicAddress(af);
|
||||
std::vector<std::string> ret(addrs.size());
|
||||
std::transform(addrs.begin(), addrs.end(), ret.begin(), dht::printAddr);
|
||||
std::transform(addrs.begin(), addrs.end(), ret.begin(), [](const SockAddr& a) { return a.toString(); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::registerCertificate(std::shared_ptr<crypto::Certificate> cert) {
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_->registerCertificate(cert);
|
||||
activeDht()->registerCertificate(cert);
|
||||
}
|
||||
void
|
||||
DhtRunner::setLocalCertificateStore(CertificateStoreQuery&& query_method) {
|
||||
std::lock_guard<std::mutex> lck(dht_mtx);
|
||||
dht_->setLocalCertificateStore(std::forward<CertificateStoreQuery>(query_method));
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->setLocalCertificateStore(std::forward<CertificateStoreQuery>(query_method));
|
||||
#endif
|
||||
if (dht_)
|
||||
dht_->setLocalCertificateStore(std::forward<CertificateStoreQuery>(query_method));
|
||||
}
|
||||
|
||||
time_point
|
||||
DhtRunner::loop_()
|
||||
{
|
||||
if (!dht_)
|
||||
auto dht = activeDht();
|
||||
if (not dht)
|
||||
return {};
|
||||
|
||||
decltype(pending_ops) ops {};
|
||||
@@ -332,7 +370,7 @@ DhtRunner::loop_()
|
||||
std::move(pending_ops) : std::move(pending_ops_prio);
|
||||
}
|
||||
while (not ops.empty()) {
|
||||
ops.front()(*dht_);
|
||||
ops.front()(*dht);
|
||||
ops.pop();
|
||||
}
|
||||
|
||||
@@ -347,15 +385,15 @@ DhtRunner::loop_()
|
||||
for (const auto& pck : received) {
|
||||
auto& buf = pck.first;
|
||||
auto& from = pck.second;
|
||||
wakeup = dht_->periodic(buf.data(), buf.size()-1, from);
|
||||
wakeup = dht->periodic(buf.data(), buf.size()-1, from);
|
||||
}
|
||||
received.clear();
|
||||
} else {
|
||||
wakeup = dht_->periodic(nullptr, 0, nullptr, 0);
|
||||
wakeup = dht->periodic(nullptr, 0, nullptr, 0);
|
||||
}
|
||||
|
||||
NodeStatus nstatus4 = dht_->getStatus(AF_INET);
|
||||
NodeStatus nstatus6 = dht_->getStatus(AF_INET6);
|
||||
NodeStatus nstatus4 = dht->getStatus(AF_INET);
|
||||
NodeStatus nstatus6 = dht->getStatus(AF_INET6);
|
||||
if (nstatus4 != status4 || nstatus6 != status6) {
|
||||
status4 = nstatus4;
|
||||
status6 = nstatus6;
|
||||
@@ -375,50 +413,52 @@ DhtRunner::loop_()
|
||||
return wakeup;
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::doRun(const sockaddr_in* sin4, const sockaddr_in6* sin6, SecureDht::Config config)
|
||||
{
|
||||
dht_.reset();
|
||||
|
||||
int s4 = -1,
|
||||
s6 = -1;
|
||||
int bindSocket(const SockAddr& addr, SockAddr& bound)
|
||||
{
|
||||
bool is_ipv6 = addr.getFamily() == AF_INET6;
|
||||
int sock = socket(is_ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0)
|
||||
throw DhtException(std::string("Can't open socket: ") + strerror(sock));
|
||||
int set = 1;
|
||||
#ifdef SO_NOSIGPIPE
|
||||
setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (const char*)&set, sizeof(set));
|
||||
#endif
|
||||
if (is_ipv6)
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&set, sizeof(set));
|
||||
int rc = bind(sock, addr.get(), addr.getLength());
|
||||
if(rc < 0)
|
||||
throw DhtException("Can't bind socket on " + addr.toString() + " " + strerror(rc));
|
||||
sockaddr_storage ss;
|
||||
socklen_t ss_len = sizeof(ss);
|
||||
getsockname(sock, (sockaddr*)&ss, &ss_len);
|
||||
bound = {ss, ss_len};
|
||||
return sock;
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::startNetwork(const SockAddr sin4, const SockAddr sin6)
|
||||
{
|
||||
running_network = false;
|
||||
if (rcv_thread.joinable())
|
||||
rcv_thread.join();
|
||||
s4 = -1;
|
||||
s6 = -1;
|
||||
|
||||
bound4 = {};
|
||||
if (sin4) {
|
||||
s4 = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if(s4 >= 0) {
|
||||
int rc = bind(s4, (sockaddr*)sin4, sizeof(sockaddr_in));
|
||||
if(rc < 0)
|
||||
throw DhtException("Can't bind IPv4 socket on " + dht::print_addr((sockaddr*)sin4, sizeof(sockaddr_in)));
|
||||
bound4.second = sizeof(bound4.first);
|
||||
getsockname(s4, (sockaddr*)&bound4.first, &bound4.second);
|
||||
}
|
||||
}
|
||||
if (sin4)
|
||||
s4 = bindSocket(sin4, bound4);
|
||||
|
||||
#if 1
|
||||
bound6 = {};
|
||||
if (sin6) {
|
||||
s6 = socket(PF_INET6, SOCK_DGRAM, 0);
|
||||
if(s6 >= 0) {
|
||||
int val = 1;
|
||||
int rc = setsockopt(s6, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&val, sizeof(val));
|
||||
if(rc < 0)
|
||||
throw DhtException("Can't set IPV6_V6ONLY");
|
||||
|
||||
rc = bind(s6, (sockaddr*)sin6, sizeof(sockaddr_in6));
|
||||
if(rc < 0)
|
||||
throw DhtException("Can't bind IPv6 socket on " + dht::print_addr((sockaddr*)sin6, sizeof(sockaddr_in6)));
|
||||
bound6.second = sizeof(bound6.first);
|
||||
getsockname(s6, (sockaddr*)&bound6.first, &bound6.second);
|
||||
}
|
||||
}
|
||||
if (sin6)
|
||||
s6 = bindSocket(sin6, bound6);
|
||||
#endif
|
||||
|
||||
dht_ = std::unique_ptr<SecureDht>(new SecureDht {s4, s6, config});
|
||||
|
||||
rcv_thread = std::thread([this,s4,s6]() {
|
||||
running_network = true;
|
||||
rcv_thread = std::thread([this]() {
|
||||
try {
|
||||
while (true) {
|
||||
while (running_network) {
|
||||
struct timeval tv {/*.tv_sec = */0, /*.tv_usec = */250000};
|
||||
fd_set readfds;
|
||||
|
||||
@@ -436,24 +476,24 @@ DhtRunner::doRun(const sockaddr_in* sin4, const sockaddr_in6* sin6, SecureDht::C
|
||||
}
|
||||
}
|
||||
|
||||
if(!running)
|
||||
if (not running_network)
|
||||
break;
|
||||
|
||||
if(rc > 0) {
|
||||
std::array<uint8_t, 1024 * 64> buf;
|
||||
SockAddr from;
|
||||
from.second = sizeof(from.first);
|
||||
sockaddr_storage from;
|
||||
socklen_t from_len = sizeof(from);
|
||||
|
||||
if(s4 >= 0 && FD_ISSET(s4, &readfds))
|
||||
rc = recvfrom(s4, (char*)buf.data(), buf.size(), 0, (struct sockaddr*)&from.first, &from.second);
|
||||
rc = recvfrom(s4, (char*)buf.data(), buf.size(), 0, (sockaddr*)&from, &from_len);
|
||||
else if(s6 >= 0 && FD_ISSET(s6, &readfds))
|
||||
rc = recvfrom(s6, (char*)buf.data(), buf.size(), 0, (struct sockaddr*)&from.first, &from.second);
|
||||
rc = recvfrom(s6, (char*)buf.data(), buf.size(), 0, (sockaddr*)&from, &from_len);
|
||||
else
|
||||
break;
|
||||
if (rc > 0) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(sock_mtx);
|
||||
rcv.emplace_back(Blob {buf.begin(), buf.begin()+rc+1}, from);
|
||||
rcv.emplace_back(Blob {buf.begin(), buf.begin()+rc+1}, SockAddr(from, from_len));
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
@@ -466,6 +506,10 @@ DhtRunner::doRun(const sockaddr_in* sin4, const sockaddr_in6* sin6, SecureDht::C
|
||||
close(s4);
|
||||
if (s6 >= 0)
|
||||
close(s6);
|
||||
s4 = -1;
|
||||
s6 = -1;
|
||||
bound4 = {};
|
||||
bound6 = {};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -486,7 +530,8 @@ DhtRunner::get(const std::string& key, GetCallback vcb, DoneCallbackSimple dcb,
|
||||
{
|
||||
get(InfoHash::get(key), vcb, dcb, f, w);
|
||||
}
|
||||
void DhtRunner::query(const InfoHash& hash, QueryCallback cb, DoneCallback done_cb, Query q) {
|
||||
void
|
||||
DhtRunner::query(const InfoHash& hash, QueryCallback cb, DoneCallback done_cb, Query q) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops.emplace([=](SecureDht& dht) mutable {
|
||||
@@ -503,7 +548,28 @@ DhtRunner::listen(InfoHash hash, GetCallback vcb, Value::Filter f, Where w)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops.emplace([=](SecureDht& dht) mutable {
|
||||
ret_token->set_value(dht.listen(hash, vcb, std::move(f), std::move(w)));
|
||||
auto tokenbGlobal = listener_token_++;
|
||||
Listener listener {};
|
||||
listener.hash = hash;
|
||||
listener.f = std::move(f);
|
||||
listener.w = std::move(w);
|
||||
listener.gcb = [hash,vcb,tokenbGlobal,this](const std::vector<Sp<Value>>& vals){
|
||||
if (not vcb(vals)) {
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
cancelListen(hash, tokenbGlobal);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (use_proxy)
|
||||
listener.tokenProxyDht = dht.listen(hash, listener.gcb, listener.f, listener.w);
|
||||
else
|
||||
#endif
|
||||
listener.tokenClassicDht = dht.listen(hash, listener.gcb, listener.f, listener.w);
|
||||
listeners_.emplace(tokenbGlobal, std::move(listener));
|
||||
ret_token->set_value(tokenbGlobal);
|
||||
});
|
||||
}
|
||||
cv.notify_all();
|
||||
@@ -519,12 +585,22 @@ DhtRunner::listen(const std::string& key, GetCallback vcb, Value::Filter f, Wher
|
||||
void
|
||||
DhtRunner::cancelListen(InfoHash h, size_t token)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops.emplace([=](SecureDht& dht) {
|
||||
dht.cancelListen(h, token);
|
||||
});
|
||||
}
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
pending_ops.emplace([=](SecureDht&) {
|
||||
auto it = listeners_.find(token);
|
||||
if (it == listeners_.end()) return;
|
||||
if (it->second.tokenClassicDht)
|
||||
dht_->cancelListen(h, it->second.tokenClassicDht);
|
||||
if (it->second.tokenProxyDht and dht_via_proxy_)
|
||||
dht_via_proxy_->cancelListen(h, it->second.tokenProxyDht);
|
||||
listeners_.erase(it);
|
||||
});
|
||||
#else
|
||||
pending_ops.emplace([=](SecureDht& dht) {
|
||||
dht.cancelListen(h, token);
|
||||
});
|
||||
#endif // OPENDHT_PROXY_CLIENT
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
@@ -660,7 +736,7 @@ DhtRunner::tryBootstrapContinuously()
|
||||
for (auto it = nodes.rbegin(); it != nodes.rend(); it++) {
|
||||
++ping_count;
|
||||
try {
|
||||
bootstrap(getAddrInfo(it->first, it->second), [&](bool) {
|
||||
bootstrap(SockAddr::resolve(it->first, it->second), [&](bool) {
|
||||
if (not running)
|
||||
return;
|
||||
{
|
||||
@@ -691,31 +767,6 @@ DhtRunner::tryBootstrapContinuously()
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::pair<sockaddr_storage, socklen_t>>
|
||||
DhtRunner::getAddrInfo(const std::string& host, const std::string& service)
|
||||
{
|
||||
std::vector<std::pair<sockaddr_storage, socklen_t>> ips {};
|
||||
if (host.empty())
|
||||
return ips;
|
||||
|
||||
addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
addrinfo* info = nullptr;
|
||||
int rc = getaddrinfo(host.c_str(), service.c_str(), &hints, &info);
|
||||
if(rc != 0)
|
||||
throw std::invalid_argument(std::string("Error: `") + host + ":" + service + "`: " + gai_strerror(rc));
|
||||
|
||||
addrinfo* infop = info;
|
||||
while (infop) {
|
||||
ips.emplace_back(sockaddr_storage(), infop->ai_addrlen);
|
||||
std::copy_n((uint8_t*)infop->ai_addr, infop->ai_addrlen, (uint8_t*)&ips.back().first);
|
||||
infop = infop->ai_next;
|
||||
}
|
||||
freeaddrinfo(info);
|
||||
return ips;
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::bootstrap(const std::string& host, const std::string& service)
|
||||
{
|
||||
@@ -733,13 +784,13 @@ DhtRunner::clearBootstrap()
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::bootstrap(const std::vector<std::pair<sockaddr_storage, socklen_t>>& nodes, DoneCallbackSimple&& cb)
|
||||
DhtRunner::bootstrap(const std::vector<SockAddr>& nodes, DoneCallbackSimple&& cb)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops_prio.emplace([=](SecureDht& dht) mutable {
|
||||
auto rem = cb ? std::make_shared<std::pair<size_t, bool>>(nodes.size(), false) : nullptr;
|
||||
for (auto& node : nodes)
|
||||
dht.pingNode((sockaddr*)&node.first, node.second, cb ? [rem,cb](bool ok) {
|
||||
for (const auto& node : nodes)
|
||||
dht.pingNode(node.get(), node.getLength(), cb ? [rem,cb](bool ok) {
|
||||
auto& r = *rem;
|
||||
r.first--;
|
||||
r.second |= ok;
|
||||
@@ -755,7 +806,7 @@ DhtRunner::bootstrap(const SockAddr& addr, DoneCallbackSimple&& cb)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops_prio.emplace([addr,cb](SecureDht& dht) mutable {
|
||||
dht.pingNode((const sockaddr*)&addr.first, addr.second, std::move(cb));
|
||||
dht.pingNode(addr.get(), addr.getLength(), std::move(cb));
|
||||
});
|
||||
cv.notify_all();
|
||||
}
|
||||
@@ -796,4 +847,119 @@ DhtRunner::findCertificate(InfoHash hash, std::function<void(const std::shared_p
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::resetDht()
|
||||
{
|
||||
listeners_.clear();
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
dht_via_proxy_.reset();
|
||||
#endif // OPENDHT_PROXY_CLIENT
|
||||
dht_.reset();
|
||||
}
|
||||
|
||||
SecureDht*
|
||||
DhtRunner::activeDht() const
|
||||
{
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
return use_proxy? dht_via_proxy_.get() : dht_.get();
|
||||
#else
|
||||
return dht_.get();
|
||||
#endif // OPENDHT_PROXY_CLIENT
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::enableProxy(bool proxify)
|
||||
{
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (dht_via_proxy_) {
|
||||
dht_via_proxy_->shutdown({});
|
||||
}
|
||||
if (proxify) {
|
||||
// Init the proxy client
|
||||
auto dht_via_proxy = std::unique_ptr<DhtInterface>(
|
||||
new DhtProxyClient([this]{
|
||||
if (config_.threaded) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
pending_ops_prio.emplace([=](SecureDht&) mutable {});
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
}, config_.proxy_server, config_.push_node_id)
|
||||
);
|
||||
dht_via_proxy_ = std::unique_ptr<SecureDht>(new SecureDht(std::move(dht_via_proxy), config_.dht_config));
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
if (not pushToken_.empty())
|
||||
dht_via_proxy_->setPushNotificationToken(pushToken_);
|
||||
#endif
|
||||
// add current listeners
|
||||
for (auto& l: listeners_)
|
||||
l.second.tokenProxyDht = dht_via_proxy_->listen(l.second.hash, l.second.gcb, l.second.f, l.second.w);
|
||||
// and use it
|
||||
use_proxy = proxify;
|
||||
} else {
|
||||
use_proxy = proxify;
|
||||
std::lock_guard<std::mutex> lck(storage_mtx);
|
||||
if (not listeners_.empty()) {
|
||||
pending_ops.emplace([this](SecureDht& /*dht*/) mutable {
|
||||
if (not dht_)
|
||||
return;
|
||||
for (auto& l : listeners_) {
|
||||
if (not l.second.tokenClassicDht) {
|
||||
l.second.tokenClassicDht = dht_->listen(l.second.hash, l.second.gcb, l.second.f, l.second.w);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (proxify)
|
||||
std::cerr << "DHT proxy requested but OpenDHT built without proxy support." << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the push notification device token
|
||||
*/
|
||||
void
|
||||
DhtRunner::setPushNotificationToken(const std::string& token) {
|
||||
#if OPENDHT_PROXY_CLIENT && OPENDHT_PUSH_NOTIFICATIONS
|
||||
pushToken_ = token;
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->setPushNotificationToken(token);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::forwardAllMessages(bool forward)
|
||||
{
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->forwardAllMessages(forward);
|
||||
#endif // OPENDHT_PROXY_CLIENT
|
||||
if (dht_)
|
||||
dht_->forwardAllMessages(forward);
|
||||
#endif // OPENDHT_PROXY_SERVER
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::pushNotificationReceived(const std::map<std::string, std::string>& data) const
|
||||
{
|
||||
#if OPENDHT_PROXY_CLIENT && OPENDHT_PUSH_NOTIFICATIONS
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->pushNotificationReceived(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunner::resubscribe(unsigned token)
|
||||
{
|
||||
#if OPENDHT_PROXY_CLIENT && OPENDHT_PUSH_NOTIFICATIONS
|
||||
if (dht_via_proxy_)
|
||||
dht_via_proxy_->resubscribe(token);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
+23
-91
@@ -17,11 +17,6 @@
|
||||
*/
|
||||
|
||||
#include "infohash.h"
|
||||
#include "rng.h"
|
||||
|
||||
extern "C" {
|
||||
#include <gnutls/gnutls.h>
|
||||
}
|
||||
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
@@ -29,95 +24,32 @@ extern "C" {
|
||||
|
||||
namespace dht {
|
||||
|
||||
InfoHash::InfoHash(const std::string& hex) {
|
||||
if (hex.size() < 2*HASH_LEN) {
|
||||
fill(0);
|
||||
return;
|
||||
}
|
||||
const auto p = (const char*)hex.data();
|
||||
for (size_t i = 0; i < HASH_LEN; i++) {
|
||||
unsigned res = 0;
|
||||
sscanf(p + 2*i, "%02x", &res);
|
||||
(*this)[i] = res;
|
||||
}
|
||||
const HexMap hex_map = {};
|
||||
|
||||
void
|
||||
NodeExport::msgpack_unpack(msgpack::object o)
|
||||
{
|
||||
if (o.type != msgpack::type::MAP)
|
||||
throw msgpack::type_error();
|
||||
if (o.via.map.size < 2)
|
||||
throw msgpack::type_error();
|
||||
if (o.via.map.ptr[0].key.as<std::string>() != "id")
|
||||
throw msgpack::type_error();
|
||||
if (o.via.map.ptr[1].key.as<std::string>() != "addr")
|
||||
throw msgpack::type_error();
|
||||
const auto& addr = o.via.map.ptr[1].val;
|
||||
if (addr.type != msgpack::type::BIN)
|
||||
throw msgpack::type_error();
|
||||
if (addr.via.bin.size > sizeof(sockaddr_storage))
|
||||
throw msgpack::type_error();
|
||||
id.msgpack_unpack(o.via.map.ptr[0].val);
|
||||
sslen = addr.via.bin.size;
|
||||
std::copy_n(addr.via.bin.ptr, addr.via.bin.size, (char*)&ss);
|
||||
}
|
||||
|
||||
InfoHash
|
||||
InfoHash::get(const uint8_t* data, size_t data_len)
|
||||
std::ostream& operator<< (std::ostream& s, const NodeExport& h)
|
||||
{
|
||||
InfoHash h;
|
||||
size_t s = h.size();
|
||||
const gnutls_datum_t gnudata = {(uint8_t*)data, (unsigned)data_len};
|
||||
const gnutls_digest_algorithm_t algo = (HASH_LEN == 64) ? GNUTLS_DIG_SHA512 : (
|
||||
(HASH_LEN == 32) ? GNUTLS_DIG_SHA256 : (
|
||||
(HASH_LEN == 20) ? GNUTLS_DIG_SHA1 :
|
||||
GNUTLS_DIG_NULL ));
|
||||
static_assert(algo != GNUTLS_DIG_NULL, "Can't find hash function to use.");
|
||||
int rc = gnutls_fingerprint(algo, &gnudata, h.data(), &s);
|
||||
if (rc == 0 && s == HASH_LEN)
|
||||
return h;
|
||||
throw std::runtime_error(std::string("Error hashing: ") + gnutls_strerror(rc));
|
||||
}
|
||||
|
||||
InfoHash
|
||||
InfoHash::getRandom()
|
||||
{
|
||||
InfoHash h;
|
||||
crypto::random_device rdev;
|
||||
std::uniform_int_distribution<uint32_t> rand_int;
|
||||
auto a = reinterpret_cast<uint32_t*>(h.data());
|
||||
auto b = reinterpret_cast<uint32_t*>(h.data() + h.size());
|
||||
std::generate(a, b, std::bind(rand_int, std::ref(rdev)));
|
||||
return h;
|
||||
}
|
||||
|
||||
struct HexMap : public std::array<std::array<char, 2>, 256> {
|
||||
HexMap() {
|
||||
for (size_t i=0; i<size(); i++) {
|
||||
auto& e = (*this)[i];
|
||||
e[0] = hex_digits[(i >> 4) & 0x0F];
|
||||
e[1] = hex_digits[i & 0x0F];
|
||||
}
|
||||
}
|
||||
private:
|
||||
static constexpr const char* hex_digits = "0123456789abcdef";
|
||||
};
|
||||
|
||||
const char*
|
||||
InfoHash::to_c_str() const
|
||||
{
|
||||
static const HexMap map;
|
||||
thread_local std::array<char, HASH_LEN*2+1> buf;
|
||||
for (size_t i=0; i<HASH_LEN; i++) {
|
||||
auto b = buf.data()+i*2;
|
||||
const auto& m = map[(*this)[i]];
|
||||
*((uint16_t*)b) = *((uint16_t*)&m);
|
||||
}
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
std::string
|
||||
InfoHash::toString() const
|
||||
{
|
||||
return std::string(to_c_str(), HASH_LEN*2);
|
||||
}
|
||||
|
||||
std::ostream& operator<< (std::ostream& s, const InfoHash& h)
|
||||
{
|
||||
s.write(h.to_c_str(), HASH_LEN*2);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::istream& operator>> (std::istream& s, InfoHash& h)
|
||||
{
|
||||
std::array<char, HASH_LEN*2> dat;
|
||||
s.exceptions(std::istream::eofbit | std::istream::failbit);
|
||||
s.read(&(*dat.begin()), dat.size());
|
||||
for (size_t i = 0; i < HASH_LEN; i++) {
|
||||
unsigned res = 0;
|
||||
sscanf(dat.data() + 2*i, "%02x", &res);
|
||||
h[i] = res;
|
||||
}
|
||||
msgpack::pack(s, h);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace dht {
|
||||
namespace net {
|
||||
|
||||
enum class MessageType {
|
||||
Error = 0,
|
||||
Reply,
|
||||
Ping,
|
||||
FindNode,
|
||||
GetValues,
|
||||
AnnounceValue,
|
||||
Refresh,
|
||||
Listen,
|
||||
ValueData,
|
||||
ValueUpdate
|
||||
};
|
||||
|
||||
} /* namespace net */
|
||||
} /* dht */
|
||||
+187
-493
File diff suppressed because it is too large.
Load diff
+73
-23
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "node.h"
|
||||
#include "request.h"
|
||||
#include "rng.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -29,6 +30,14 @@ constexpr std::chrono::minutes Node::NODE_EXPIRE_TIME;
|
||||
constexpr std::chrono::minutes Node::NODE_GOOD_TIME;
|
||||
constexpr std::chrono::seconds Node::MAX_RESPONSE_TIME;
|
||||
|
||||
|
||||
Node::Node(const InfoHash& id, const SockAddr& addr, bool client)
|
||||
: id(id), addr(addr), is_client(client), sockets_()
|
||||
{
|
||||
crypto::random_device rd;
|
||||
transaction_id = std::uniform_int_distribution<Tid>{1}(rd);
|
||||
}
|
||||
|
||||
/* This is our definition of a known-good node. */
|
||||
bool
|
||||
Node::isGood(time_point now) const
|
||||
@@ -41,11 +50,9 @@ Node::isGood(time_point now) const
|
||||
bool
|
||||
Node::isPendingMessage() const
|
||||
{
|
||||
for (auto w : requests_) {
|
||||
if (auto r = w.lock()) {
|
||||
if (r->pending())
|
||||
return true;
|
||||
}
|
||||
for (auto r : requests_) {
|
||||
if (r.second->pending())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -54,11 +61,9 @@ size_t
|
||||
Node::getPendingMessageCount() const
|
||||
{
|
||||
size_t count {0};
|
||||
for (auto w : requests_) {
|
||||
if (auto r = w.lock()) {
|
||||
if (r->pending())
|
||||
count++;
|
||||
}
|
||||
for (auto r : requests_) {
|
||||
if (r.second->pending())
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -71,27 +76,44 @@ Node::update(const SockAddr& new_addr)
|
||||
|
||||
/** To be called when a message was sent to the node */
|
||||
void
|
||||
Node::requested(std::shared_ptr<net::Request>& req)
|
||||
Node::requested(const Sp<net::Request>& req)
|
||||
{
|
||||
requests_.emplace_back(req);
|
||||
auto e = requests_.emplace(req->getTid(), req);
|
||||
if (not e.second and req != e.first->second) {
|
||||
// Should not happen !
|
||||
// Try to handle this scenario as well as we can
|
||||
e.first->second->setExpired();
|
||||
e.first->second = req;
|
||||
}
|
||||
}
|
||||
|
||||
/** To be called when a message was received from the node.
|
||||
Req should be true if the message was an aswer to a request we made*/
|
||||
void
|
||||
Node::received(time_point now, std::shared_ptr<net::Request> req)
|
||||
Node::received(time_point now, const Sp<net::Request>& req)
|
||||
{
|
||||
time = now;
|
||||
expired_ = false;
|
||||
if (req) {
|
||||
reply_time = now;
|
||||
for (auto it = requests_.begin(); it != requests_.end();) {
|
||||
auto r = it->lock();
|
||||
if (not r or r == req)
|
||||
it = requests_.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
requests_.erase(req->getTid());
|
||||
}
|
||||
}
|
||||
|
||||
Sp<net::Request>
|
||||
Node::getRequest(Tid tid)
|
||||
{
|
||||
auto it = requests_.find(tid);
|
||||
return it != requests_.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
Node::cancelRequest(const Sp<net::Request>& req)
|
||||
{
|
||||
if (req) {
|
||||
req->cancel();
|
||||
closeSocket(req->closeSocket());
|
||||
requests_.erase(req->getTid());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +121,41 @@ void
|
||||
Node::setExpired()
|
||||
{
|
||||
expired_ = true;
|
||||
for (auto w : requests_) {
|
||||
if (auto r = w.lock())
|
||||
r->setExpired();
|
||||
for (auto r : requests_) {
|
||||
r.second->setExpired();
|
||||
}
|
||||
requests_.clear();
|
||||
sockets_.clear();
|
||||
}
|
||||
|
||||
Tid
|
||||
Node::openSocket(SocketCb&& cb)
|
||||
{
|
||||
if (++transaction_id == 0)
|
||||
transaction_id = 1;
|
||||
|
||||
auto sock = std::make_shared<Socket>(std::move(cb));
|
||||
auto s = sockets_.emplace(transaction_id, std::move(sock));
|
||||
if (not s.second)
|
||||
s.first->second = std::move(sock);
|
||||
return transaction_id;
|
||||
}
|
||||
|
||||
Sp<Socket>
|
||||
Node::getSocket(Tid id)
|
||||
{
|
||||
auto it = sockets_.find(id);
|
||||
return it == sockets_.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
void
|
||||
Node::closeSocket(Tid id)
|
||||
{
|
||||
if (id) {
|
||||
sockets_.erase(id);
|
||||
//DHT_LOG.w("Closing socket (tid: %d), %lu remaining", socket->id, sockets_.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Node::toString() const
|
||||
|
||||
+45
-28
@@ -20,44 +20,53 @@
|
||||
|
||||
namespace dht {
|
||||
|
||||
std::shared_ptr<Node>
|
||||
NodeCache::~NodeCache()
|
||||
{
|
||||
cache_4.setExpired();
|
||||
cache_6.setExpired();
|
||||
}
|
||||
|
||||
Sp<Node>
|
||||
NodeCache::getNode(const InfoHash& id, sa_family_t family) {
|
||||
return (family == AF_INET ? cache_4 : cache_6).getNode(id);
|
||||
return cache(family).getNode(id);
|
||||
}
|
||||
|
||||
std::shared_ptr<Node>
|
||||
NodeCache::getNode(const InfoHash& id, const SockAddr& addr, time_point now, bool confirm) {
|
||||
if (id == zeroes)
|
||||
Sp<Node>
|
||||
NodeCache::getNode(const InfoHash& id, const SockAddr& addr, time_point now, bool confirm, bool client) {
|
||||
if (not id)
|
||||
return std::make_shared<Node>(id, addr);
|
||||
return (addr.getFamily() == AF_INET ? cache_4 : cache_6).getNode(id, addr, now, confirm);
|
||||
return cache(addr.getFamily()).getNode(id, addr, now, confirm, client);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Node>>
|
||||
NodeCache::getCachedNodes(const InfoHash& id, sa_family_t sa_f, size_t count) {
|
||||
const auto& c = (sa_f == AF_INET ? cache_4 : cache_6);
|
||||
auto it_p = c.lower_bound(id),
|
||||
it_n = it_p;
|
||||
std::vector<Sp<Node>>
|
||||
NodeCache::getCachedNodes(const InfoHash& id, sa_family_t sa_f, size_t count) const
|
||||
{
|
||||
const auto& c = cache(sa_f);
|
||||
|
||||
std::vector<std::shared_ptr<Node>> nodes;
|
||||
std::vector<Sp<Node>> nodes;
|
||||
nodes.reserve(std::min(c.size(), count));
|
||||
NodeMap::const_iterator it;
|
||||
auto dec_it = [&c](NodeMap::const_iterator& it) {
|
||||
auto ret = it;
|
||||
it = (it == c.cbegin()) ? c.cend() : std::prev(it);
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (it_p != c.begin()) /* Create 2 separate iterator if we could */
|
||||
--it_p;
|
||||
auto it_p = c.lower_bound(id),
|
||||
it_n = it_p;
|
||||
if (not c.empty())
|
||||
dec_it(it_p); /* Create 2 separate iterator if we could */
|
||||
|
||||
while (nodes.size() < count and (it_n != c.end() or it_p != c.end())) {
|
||||
while (nodes.size() < count and (it_n != c.cend() or it_p != c.cend())) {
|
||||
/* If one of the iterator is at the end, then take the other one
|
||||
If they are both in middle of somewhere comapre both and take
|
||||
the closest to the id. */
|
||||
if (it_p == c.end()) it = it_n++;
|
||||
else if (it_n == c.end()) it = it_p--;
|
||||
else it = id.xorCmp(it_p->first, it_n->first) < 0 ? it_p-- : it_n++;
|
||||
|
||||
if (it == c.begin())
|
||||
it_p = c.end();
|
||||
if (it_p == c.cend()) it = it_n++;
|
||||
else if (it_n == c.cend()) it = dec_it(it_p);
|
||||
else it = id.xorCmp(it_p->first, it_n->first) < 0 ? dec_it(it_p) : it_n++;
|
||||
|
||||
if (auto n = it->second.lock())
|
||||
if ( not n->isExpired() )
|
||||
if ( not n->isExpired() and not n->isClient() )
|
||||
nodes.emplace_back(std::move(n));
|
||||
}
|
||||
|
||||
@@ -71,11 +80,11 @@ NodeCache::clearBadNodes(sa_family_t family)
|
||||
clearBadNodes(AF_INET);
|
||||
clearBadNodes(AF_INET6);
|
||||
} else {
|
||||
(family == AF_INET ? cache_4 : cache_6).clearBadNodes();
|
||||
cache(family).clearBadNodes();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Node>
|
||||
Sp<Node>
|
||||
NodeCache::NodeMap::getNode(const InfoHash& id)
|
||||
{
|
||||
auto wn = find(id);
|
||||
@@ -87,15 +96,15 @@ NodeCache::NodeMap::getNode(const InfoHash& id)
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Node>
|
||||
NodeCache::NodeMap::getNode(const InfoHash& id, const SockAddr& addr, time_point now, bool confirm)
|
||||
Sp<Node>
|
||||
NodeCache::NodeMap::getNode(const InfoHash& id, const SockAddr& addr, time_point now, bool confirm, bool client)
|
||||
{
|
||||
auto it = emplace(id, std::weak_ptr<Node>{});
|
||||
auto node = it.first->second.lock();
|
||||
if (not node) {
|
||||
node = std::make_shared<Node>(id, addr);
|
||||
node = std::make_shared<Node>(id, addr, client);
|
||||
it.first->second = node;
|
||||
} else if (confirm || node->time < now - Node::NODE_EXPIRE_TIME) {
|
||||
} else if (confirm or node->isOld(now)) {
|
||||
node->update(addr);
|
||||
}
|
||||
return node;
|
||||
@@ -113,4 +122,12 @@ NodeCache::NodeMap::clearBadNodes() {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NodeCache::NodeMap::setExpired() {
|
||||
for (auto& wn : *this)
|
||||
if (auto n = wn.second.lock())
|
||||
n->setExpired();
|
||||
clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "infohash.h"
|
||||
#include "sockaddr.h"
|
||||
#include "net.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace dht {
|
||||
namespace net {
|
||||
|
||||
Tid unpackTid(const msgpack::object& o) {
|
||||
switch (o.type) {
|
||||
case msgpack::type::POSITIVE_INTEGER:
|
||||
return o.as<Tid>();
|
||||
default:
|
||||
return ntohl(*reinterpret_cast<const uint32_t*>(o.as<std::array<char, 4>>().data()));
|
||||
}
|
||||
}
|
||||
|
||||
struct ParsedMessage {
|
||||
MessageType type;
|
||||
/* Node ID of the sender */
|
||||
InfoHash id;
|
||||
/* Network id */
|
||||
NetId network {0};
|
||||
/** Is a client node */
|
||||
bool is_client {false};
|
||||
/* hash for which values are requested */
|
||||
InfoHash info_hash;
|
||||
/* target id around which to find nodes */
|
||||
InfoHash target;
|
||||
/* transaction id */
|
||||
Tid tid;
|
||||
/* tid for packets going through request socket */
|
||||
Tid socket_id;
|
||||
/* security token */
|
||||
Blob token;
|
||||
/* the value id (announce confirmation) */
|
||||
Value::Id value_id;
|
||||
/* time when value was first created */
|
||||
time_point created { time_point::max() };
|
||||
/* IPv4 nodes in response to a 'find' request */
|
||||
Blob nodes4_raw, nodes6_raw;
|
||||
std::vector<Sp<Node>> nodes4, nodes6;
|
||||
/* values to store or retreive request */
|
||||
std::vector<Sp<Value>> values;
|
||||
/* index for fields values */
|
||||
std::vector<Sp<FieldValueIndex>> fields;
|
||||
/** When part of the message header: {index -> (total size, {})}
|
||||
* When part of partial value data: {index -> (offset, part_data)} */
|
||||
std::map<unsigned, std::pair<unsigned, Blob>> value_parts;
|
||||
/* query describing a filter to apply on values. */
|
||||
Query query;
|
||||
/* states if ipv4 or ipv6 request */
|
||||
want_t want;
|
||||
/* error code in case of error */
|
||||
uint16_t error_code;
|
||||
/* reported address by the distant node */
|
||||
std::string ua;
|
||||
SockAddr addr;
|
||||
void msgpack_unpack(msgpack::object o);
|
||||
|
||||
bool append(const ParsedMessage& block);
|
||||
bool complete();
|
||||
};
|
||||
|
||||
bool
|
||||
ParsedMessage::append(const ParsedMessage& block)
|
||||
{
|
||||
bool ret(false);
|
||||
for (const auto& ve : block.value_parts) {
|
||||
auto part_val = value_parts.find(ve.first);
|
||||
if (part_val == value_parts.end()
|
||||
|| part_val->second.second.size() >= part_val->second.first)
|
||||
continue;
|
||||
// TODO: handle out-of-order packets
|
||||
if (ve.second.first != part_val->second.second.size()) {
|
||||
//std::cout << "skipping out-of-order packet" << std::endl;
|
||||
continue;
|
||||
}
|
||||
ret = true;
|
||||
part_val->second.second.insert(part_val->second.second.end(),
|
||||
ve.second.second.begin(),
|
||||
ve.second.second.end());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
ParsedMessage::complete()
|
||||
{
|
||||
for (auto& e : value_parts) {
|
||||
//std::cout << "part " << e.first << ": " << e.second.second.size() << "/" << e.second.first << std::endl;
|
||||
if (e.second.first > e.second.second.size())
|
||||
return false;
|
||||
}
|
||||
for (auto& e : value_parts) {
|
||||
msgpack::unpacked msg;
|
||||
msgpack::unpack(msg, (const char*)e.second.second.data(), e.second.second.size());
|
||||
values.emplace_back(std::make_shared<Value>(msg.get()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessage::msgpack_unpack(msgpack::object msg)
|
||||
{
|
||||
auto y = findMapValue(msg, "y");
|
||||
auto r = findMapValue(msg, "r");
|
||||
auto u = findMapValue(msg, "u");
|
||||
auto e = findMapValue(msg, "e");
|
||||
auto v = findMapValue(msg, "p");
|
||||
|
||||
if (auto t = findMapValue(msg, "t"))
|
||||
tid = unpackTid(*t);
|
||||
|
||||
if (auto rv = findMapValue(msg, "v"))
|
||||
ua = rv->as<std::string>();
|
||||
|
||||
if (auto netid = findMapValue(msg, "n"))
|
||||
network = netid->as<NetId>();
|
||||
|
||||
if (auto is_client_v = findMapValue(msg, "s"))
|
||||
is_client = is_client_v->as<bool>();
|
||||
|
||||
std::string q;
|
||||
if (auto rq = findMapValue(msg, "q")) {
|
||||
if (rq->type != msgpack::type::STR)
|
||||
throw msgpack::type_error();
|
||||
q = rq->as<std::string>();
|
||||
}
|
||||
|
||||
if (e)
|
||||
type = MessageType::Error;
|
||||
else if (r)
|
||||
type = MessageType::Reply;
|
||||
else if (v)
|
||||
type = MessageType::ValueData;
|
||||
else if (u)
|
||||
type = MessageType::ValueUpdate;
|
||||
else if (y and y->as<std::string>() != "q")
|
||||
throw msgpack::type_error();
|
||||
else if (q == "ping")
|
||||
type = MessageType::Ping;
|
||||
else if (q == "find")
|
||||
type = MessageType::FindNode;
|
||||
else if (q == "get")
|
||||
type = MessageType::GetValues;
|
||||
else if (q == "listen")
|
||||
type = MessageType::Listen;
|
||||
else if (q == "put")
|
||||
type = MessageType::AnnounceValue;
|
||||
else if (q == "refresh")
|
||||
type = MessageType::Refresh;
|
||||
else
|
||||
throw msgpack::type_error();
|
||||
|
||||
if (type == MessageType::ValueData) {
|
||||
if (v->type != msgpack::type::MAP)
|
||||
throw msgpack::type_error();
|
||||
for (size_t i = 0; i < v->via.map.size; ++i) {
|
||||
auto& vdat = v->via.map.ptr[i];
|
||||
auto o = findMapValue(vdat.val, "o");
|
||||
auto d = findMapValue(vdat.val, "d");
|
||||
if (not o or not d)
|
||||
continue;
|
||||
value_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto a = findMapValue(msg, "a");
|
||||
if (!a && !r && !e && !u)
|
||||
throw msgpack::type_error();
|
||||
auto& req = a ? *a : (r ? *r : (u ? *u : *e));
|
||||
|
||||
if (e) {
|
||||
if (e->type != msgpack::type::ARRAY)
|
||||
throw msgpack::type_error();
|
||||
error_code = e->via.array.ptr[0].as<uint16_t>();
|
||||
}
|
||||
|
||||
if (auto t = findMapValue(req, "sid"))
|
||||
socket_id = unpackTid(*t);
|
||||
|
||||
if (auto rid = findMapValue(req, "id"))
|
||||
id = {*rid};
|
||||
|
||||
if (auto rh = findMapValue(req, "h"))
|
||||
info_hash = {*rh};
|
||||
|
||||
if (auto rtarget = findMapValue(req, "target"))
|
||||
target = {*rtarget};
|
||||
|
||||
if (auto rquery = findMapValue(req, "q"))
|
||||
query.msgpack_unpack(*rquery);
|
||||
|
||||
if (auto otoken = findMapValue(req, "token"))
|
||||
token = unpackBlob(*otoken);
|
||||
|
||||
if (auto vid = findMapValue(req, "vid"))
|
||||
value_id = vid->as<Value::Id>();
|
||||
|
||||
if (auto rnodes4 = findMapValue(req, "n4"))
|
||||
nodes4_raw = unpackBlob(*rnodes4);
|
||||
|
||||
if (auto rnodes6 = findMapValue(req, "n6"))
|
||||
nodes6_raw = unpackBlob(*rnodes6);
|
||||
|
||||
if (auto sa = findMapValue(req, "sa")) {
|
||||
if (sa->type != msgpack::type::BIN)
|
||||
throw msgpack::type_error();
|
||||
auto l = sa->via.bin.size;
|
||||
if (l == sizeof(in_addr)) {
|
||||
addr.setFamily(AF_INET);
|
||||
auto& a = addr.getIPv4();
|
||||
a.sin_port = 0;
|
||||
std::copy_n(sa->via.bin.ptr, l, (char*)&a.sin_addr);
|
||||
} else if (l == sizeof(in6_addr)) {
|
||||
addr.setFamily(AF_INET6);
|
||||
auto& a = addr.getIPv6();
|
||||
a.sin6_port = 0;
|
||||
std::copy_n(sa->via.bin.ptr, l, (char*)&a.sin6_addr);
|
||||
}
|
||||
} else
|
||||
addr = {};
|
||||
|
||||
if (auto rcreated = findMapValue(req, "c"))
|
||||
created = from_time_t(rcreated->as<std::time_t>());
|
||||
|
||||
if (auto rvalues = findMapValue(req, "values")) {
|
||||
if (rvalues->type != msgpack::type::ARRAY)
|
||||
throw msgpack::type_error();
|
||||
for (size_t i = 0; i < rvalues->via.array.size; i++) {
|
||||
auto& packed_v = rvalues->via.array.ptr[i];
|
||||
if (packed_v.type == msgpack::type::POSITIVE_INTEGER) {
|
||||
// Skip oversize values with a small margin for header overhead
|
||||
if (packed_v.via.u64 > MAX_VALUE_SIZE + 32)
|
||||
continue;
|
||||
value_parts.emplace(i, std::make_pair(packed_v.via.u64, Blob{}));
|
||||
} else {
|
||||
try {
|
||||
values.emplace_back(std::make_shared<Value>(rvalues->via.array.ptr[i]));
|
||||
} catch (const std::exception& e) {
|
||||
//DHT_LOG_WARN("Error reading value: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (auto raw_fields = findMapValue(req, "fields")) {
|
||||
if (auto rfields = findMapValue(*raw_fields, "f")) {
|
||||
auto vfields = rfields->as<std::set<Value::Field>>();
|
||||
if (auto rvalues = findMapValue(*raw_fields, "v")) {
|
||||
if (rvalues->type != msgpack::type::ARRAY)
|
||||
throw msgpack::type_error();
|
||||
size_t val_num = rvalues->via.array.size / vfields.size();
|
||||
for (size_t i = 0; i < val_num; ++i) {
|
||||
try {
|
||||
auto v = std::make_shared<FieldValueIndex>();
|
||||
v->msgpack_unpack_fields(vfields, *rvalues, i*vfields.size());
|
||||
fields.emplace_back(std::move(v));
|
||||
} catch (const std::exception& e) { }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw msgpack::type_error();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto w = findMapValue(req, "w")) {
|
||||
if (w->type != msgpack::type::ARRAY)
|
||||
throw msgpack::type_error();
|
||||
want = 0;
|
||||
for (unsigned i=0; i<w->via.array.size; i++) {
|
||||
auto& val = w->via.array.ptr[i];
|
||||
try {
|
||||
auto w = val.as<sa_family_t>();
|
||||
if (w == AF_INET)
|
||||
want |= WANT4;
|
||||
else if(w == AF_INET6)
|
||||
want |= WANT6;
|
||||
} catch (const std::exception& e) {};
|
||||
}
|
||||
} else {
|
||||
want = -1;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace net */
|
||||
} /* namespace dht */
|
||||
+16
-28
@@ -20,34 +20,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "net.h"
|
||||
#include "value.h"
|
||||
|
||||
namespace dht {
|
||||
struct Node;
|
||||
namespace net {
|
||||
|
||||
class NetworkEngine;
|
||||
struct ParsedMessage;
|
||||
|
||||
/*!
|
||||
* @class Socket
|
||||
* @brief Open route to a node for continous incoming packets.
|
||||
* @details
|
||||
* A socket lets a remote node send us continuous packets treated using a
|
||||
* given callback. This is intended to provide an easy management of
|
||||
* specific updates nodes can send. For e.g, this is used in the case of the
|
||||
* "listen" operation for treating updates a node has for a given storage.
|
||||
*/
|
||||
struct Socket {
|
||||
Socket() {}
|
||||
Socket(std::shared_ptr<Node> node,
|
||||
TransId id,
|
||||
std::function<void(const std::shared_ptr<Node>&, ParsedMessage&&)> on_receive) :
|
||||
node(node), id(id), on_receive(on_receive) {}
|
||||
|
||||
std::shared_ptr<Node> node;
|
||||
TransId id;
|
||||
std::function<void(const std::shared_ptr<Node>&, ParsedMessage&&)> on_receive {};
|
||||
};
|
||||
|
||||
/*!
|
||||
* @class Request
|
||||
* @brief An atomic request destined to a node.
|
||||
@@ -59,7 +40,7 @@ struct Socket {
|
||||
struct Request {
|
||||
friend class dht::net::NetworkEngine;
|
||||
|
||||
std::shared_ptr<Node> node {}; /* the node to whom the request is destined. */
|
||||
Sp<Node> node {}; /* the node to whom the request is destined. */
|
||||
time_point reply_time {time_point::min()}; /* time when we received the response to the request. */
|
||||
|
||||
enum class State
|
||||
@@ -78,13 +59,19 @@ struct Request {
|
||||
State getState() const { return state_; }
|
||||
|
||||
Request(State state = State::PENDING) : state_(state) {}
|
||||
Request(TransId tid,
|
||||
std::shared_ptr<Node> node,
|
||||
Request(MessageType type, Tid tid,
|
||||
Sp<Node> node,
|
||||
Blob&& msg,
|
||||
std::function<void(const Request&, ParsedMessage&&)> on_done,
|
||||
std::function<void(const Request&, bool)> on_expired,
|
||||
std::shared_ptr<Socket> socket = {}) :
|
||||
node(node), on_done(on_done), on_expired(on_expired), tid(tid), msg(std::move(msg)), socket(socket) { }
|
||||
Tid socket = 0) :
|
||||
node(node), tid(tid), type(type), on_done(on_done), on_expired(on_expired), msg(std::move(msg)), socket(socket) { }
|
||||
|
||||
Tid getTid() const { return tid; }
|
||||
MessageType getType() const { return type; }
|
||||
|
||||
Tid getSocket() { return socket; }
|
||||
Tid closeSocket() { auto ret = socket; socket = 0; return ret; }
|
||||
|
||||
void setExpired() {
|
||||
if (pending()) {
|
||||
@@ -121,6 +108,8 @@ private:
|
||||
msg.clear();
|
||||
}
|
||||
|
||||
const Tid tid {0}; /* the request id. */
|
||||
const MessageType type {};
|
||||
State state_ {State::PENDING};
|
||||
|
||||
unsigned attempt_count {0}; /* number of attempt to process the request. */
|
||||
@@ -130,9 +119,8 @@ private:
|
||||
std::function<void(const Request&, ParsedMessage&&)> on_done {};
|
||||
std::function<void(const Request&, bool)> on_expired {};
|
||||
|
||||
const TransId tid; /* the request id. */
|
||||
Blob msg {}; /* the serialized message. */
|
||||
std::shared_ptr<Socket> socket; /* the socket used for further reponses. */
|
||||
Tid socket; /* the socket used for further reponses. */
|
||||
};
|
||||
|
||||
} /* namespace net */
|
||||
|
||||
+92
-1
@@ -1,4 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "routing_table.h"
|
||||
|
||||
#include "network_engine.h"
|
||||
#include "rng.h"
|
||||
|
||||
#include <memory>
|
||||
@@ -35,6 +55,15 @@ Bucket::randomNode()
|
||||
return nodes.back();
|
||||
}
|
||||
|
||||
void Bucket::sendCachedPing(net::NetworkEngine& ne)
|
||||
{
|
||||
if (not cached)
|
||||
return;
|
||||
//DHT_LOG.d(b.cached->id, "[node %s] sending ping to cached node", cached->toString().c_str());
|
||||
ne.sendPing(cached, nullptr, nullptr);
|
||||
cached = {};
|
||||
}
|
||||
|
||||
InfoHash
|
||||
RoutingTable::randomId(const RoutingTable::const_iterator& it) const
|
||||
{
|
||||
@@ -47,7 +76,7 @@ RoutingTable::randomId(const RoutingTable::const_iterator& it) const
|
||||
|
||||
int b = bit/8;
|
||||
InfoHash id_return;
|
||||
std::copy_n(it->first.begin(), b, id_return.begin());
|
||||
std::copy_n(it->first.cbegin(), b, id_return.begin());
|
||||
id_return[b] = it->first[b] & (0xFF00 >> (bit % 8));
|
||||
id_return[b] |= rand_byte(rd) >> (bit % 8);
|
||||
for (unsigned i = b + 1; i < HASH_LEN; i++)
|
||||
@@ -172,4 +201,66 @@ RoutingTable::split(const RoutingTable::iterator& b)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
RoutingTable::onNewNode(const Sp<Node>& node, int confirm, const time_point& now, const InfoHash& myid, net::NetworkEngine& ne) {
|
||||
auto b = findBucket(node->id);
|
||||
if (b == end()) return false;
|
||||
|
||||
if (confirm == 2)
|
||||
b->time = now;
|
||||
|
||||
for (auto& n : b->nodes) {
|
||||
if (n == node)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mybucket = contains(b, myid);
|
||||
if (mybucket) {
|
||||
grow_time = now;
|
||||
//scheduler.edit(nextNodesConfirmation, now);
|
||||
}
|
||||
|
||||
if (b->nodes.size() >= TARGET_NODES) {
|
||||
/* Try to get rid of an expired node. */
|
||||
for (auto& n : b->nodes)
|
||||
if (n->isExpired()) {
|
||||
n = node;
|
||||
return true;
|
||||
}
|
||||
/* Bucket full. Ping a dubious node */
|
||||
bool dubious = false;
|
||||
for (auto& n : b->nodes) {
|
||||
/* Pick the first dubious node that we haven't pinged in the
|
||||
last 9 seconds. This gives nodes the time to reply, but
|
||||
tends to concentrate on the same nodes, so that we get rid
|
||||
of bad nodes fast. */
|
||||
if (not n->isGood(now)) {
|
||||
dubious = true;
|
||||
if (not n->isPendingMessage()) {
|
||||
//DHT_LOG.d(n->id, "[node %s] sending ping to dubious node", n->toString().c_str());
|
||||
ne.sendPing(n, nullptr, nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mybucket || (is_client and depth(b) < 6)) && (!dubious || size() == 1)) {
|
||||
//DHT_LOG.d("Splitting from depth %u", depth(b));
|
||||
b->sendCachedPing(ne);
|
||||
split(b);
|
||||
return onNewNode(node, confirm, now, myid, ne);
|
||||
}
|
||||
|
||||
/* No space for this node. Cache it away for later. */
|
||||
if (confirm or not b->cached)
|
||||
b->cached = node;
|
||||
} else {
|
||||
/* Create a new node. */
|
||||
b->nodes.emplace_front(node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+67
-8
@@ -76,7 +76,19 @@ struct Dht::SearchNode {
|
||||
node is a new candidate for inclusion. */
|
||||
|
||||
SearchNode() : node() {}
|
||||
SearchNode(const SearchNode&) = delete;
|
||||
SearchNode(SearchNode&&) = default;
|
||||
SearchNode& operator=(const SearchNode&) = delete;
|
||||
SearchNode& operator=(SearchNode&&) = default;
|
||||
|
||||
SearchNode(const Sp<Node>& node) : node(node) {}
|
||||
~SearchNode() {
|
||||
if (node) {
|
||||
cancelGet();
|
||||
cancelListen();
|
||||
cancelAnnounce();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can we use this node to listen/announce now ?
|
||||
@@ -173,6 +185,15 @@ struct Dht::SearchNode {
|
||||
}
|
||||
}
|
||||
|
||||
void cancelGet() {
|
||||
for (const auto& status : getStatus) {
|
||||
if (status.second->pending()) {
|
||||
node->cancelRequest(status.second);
|
||||
}
|
||||
}
|
||||
getStatus.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if a request in the status map is expired.
|
||||
*
|
||||
@@ -180,12 +201,12 @@ struct Dht::SearchNode {
|
||||
*
|
||||
* @return true if there exists an expired request, else false.
|
||||
*/
|
||||
bool expired(const SyncStatus& status) const {
|
||||
/*static bool expired(const SyncStatus& status) const {
|
||||
return std::find_if(status.begin(), status.end(),
|
||||
[](const SyncStatus::value_type& r){
|
||||
return r.second and r.second->expired();
|
||||
}) != status.end();
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Tells if a request in the status map is pending.
|
||||
@@ -194,19 +215,30 @@ struct Dht::SearchNode {
|
||||
*
|
||||
* @return true if there exists an expired request, else false.
|
||||
*/
|
||||
bool pending(const SyncStatus& status) const {
|
||||
static bool pending(const SyncStatus& status) {
|
||||
return std::find_if(status.begin(), status.end(),
|
||||
[](const SyncStatus::value_type& r){
|
||||
return r.second and r.second->pending();
|
||||
}) != status.end();
|
||||
}
|
||||
|
||||
bool pendingGet() const { return pending(getStatus); }
|
||||
|
||||
bool isAnnounced(Value::Id vid) const {
|
||||
auto ack = acked.find(vid);
|
||||
if (ack == acked.end() or not ack->second.first)
|
||||
return false;
|
||||
return ack->second.first->completed();
|
||||
}
|
||||
void cancelAnnounce() {
|
||||
for (const auto& status : acked) {
|
||||
const auto& req = status.second.first;
|
||||
if (req and req->pending()) {
|
||||
node->cancelRequest(req);
|
||||
}
|
||||
}
|
||||
acked.clear();
|
||||
}
|
||||
|
||||
bool isListening(time_point now) const {
|
||||
auto ls = listenStatus.begin();
|
||||
@@ -229,6 +261,18 @@ struct Dht::SearchNode {
|
||||
return false;
|
||||
return listen_status->second->reply_time + LISTEN_EXPIRE_TIME > now;
|
||||
}
|
||||
void cancelListen() {
|
||||
for (const auto& status : listenStatus)
|
||||
node->cancelRequest(status.second);
|
||||
listenStatus.clear();
|
||||
}
|
||||
void cancelListen(const Sp<Query>& query) {
|
||||
auto it = listenStatus.find(query);
|
||||
if (it != listenStatus.end()) {
|
||||
node->cancelRequest(it->second);
|
||||
listenStatus.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumng the node is synced, should a "put" request be sent to this node now ?
|
||||
@@ -319,7 +363,7 @@ struct Dht::Search {
|
||||
unsigned currentlySolicitedNodeCount() const {
|
||||
unsigned count = 0;
|
||||
for (const auto& n : nodes)
|
||||
if (not n.isBad() and n.pending(n.getStatus))
|
||||
if (not n.isBad() and n.pendingGet())
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
@@ -382,6 +426,21 @@ struct Dht::Search {
|
||||
bool isAnnounced(Value::Id id) const;
|
||||
bool isListening(time_point now) const;
|
||||
|
||||
void cancelListen(size_t token) {
|
||||
Sp<Query> query;
|
||||
const auto& ll = listeners.find(token);
|
||||
if (ll != listeners.cend()) {
|
||||
query = ll->second.query;
|
||||
listeners.erase(ll);
|
||||
}
|
||||
for (auto& sn : nodes) {
|
||||
if (listeners.empty())
|
||||
sn.cancelListen();
|
||||
else if (query)
|
||||
sn.cancelListen(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of non-good search nodes.
|
||||
*/
|
||||
@@ -430,10 +489,10 @@ struct Dht::Search {
|
||||
*
|
||||
* @return true if a node has been removed, else false.
|
||||
*/
|
||||
bool removeExpiredNode(time_point now) {
|
||||
bool removeExpiredNode(const time_point& now) {
|
||||
for (auto e = nodes.cend(); e != nodes.cbegin();) {
|
||||
const Node& n = *(--e)->node;
|
||||
if (n.isExpired() and n.time + Node::NODE_EXPIRE_TIME < now) {
|
||||
if (n.isRemovable(now)) {
|
||||
//std::cout << "Removing expired node " << n.id << " from IPv" << (af==AF_INET?'4':'6') << " search " << id << std::endl;
|
||||
nodes.erase(e);
|
||||
return true;
|
||||
@@ -585,7 +644,7 @@ Dht::Search::insertNode(const Sp<Node>& snode, time_point now, const Blob& token
|
||||
step_time = time_point::min();
|
||||
}
|
||||
n = nodes.insert(n, SearchNode(snode));
|
||||
node.time = now;
|
||||
node.setTime(now);
|
||||
new_search_node = true;
|
||||
if (node.isExpired()) {
|
||||
if (not expired)
|
||||
@@ -672,7 +731,7 @@ Dht::Search::getUpdateTime(time_point now) const
|
||||
for (const auto& sn : nodes) {
|
||||
if (sn.node->isExpired() or (sn.candidate and t >= TARGET_NODES))
|
||||
continue;
|
||||
auto pending = sn.pending(sn.getStatus);
|
||||
auto pending = sn.pendingGet();
|
||||
if (sn.last_get_reply < std::max(now - Node::NODE_EXPIRE_TIME, last_get) or pending) {
|
||||
// not isSynced
|
||||
if (not pending and solicited_nodes < MAX_REQUESTED_SEARCH_NODES)
|
||||
|
||||
+23
-27
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
* Author : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -32,20 +33,10 @@ extern "C" {
|
||||
|
||||
namespace dht {
|
||||
|
||||
Config& getConfig(SecureDht::Config& conf)
|
||||
SecureDht::SecureDht(std::unique_ptr<DhtInterface> dht, SecureDht::Config conf)
|
||||
: dht_(std::move(dht)), key_(conf.id.first), certificate_(conf.id.second)
|
||||
{
|
||||
auto& c = conf.node_config;
|
||||
if (c.node_id == InfoHash() and conf.id.second)
|
||||
c.node_id = InfoHash::get("node:"+conf.id.second->getId().toString());
|
||||
return c;
|
||||
}
|
||||
|
||||
SecureDht::SecureDht(int s, int s6, SecureDht::Config conf)
|
||||
: Dht(s, s6, getConfig(conf)), key_(conf.id.first), certificate_(conf.id.second)
|
||||
{
|
||||
if (s < 0 && s6 < 0)
|
||||
return;
|
||||
|
||||
if (!dht_) return;
|
||||
for (const auto& type : DEFAULT_TYPES)
|
||||
registerType(type);
|
||||
|
||||
@@ -59,7 +50,7 @@ SecureDht::SecureDht(int s, int s6, SecureDht::Config conf)
|
||||
if (key_ and certId != key_->getPublicKey().getId())
|
||||
throw DhtException("SecureDht: provided certificate doesn't match private key.");
|
||||
|
||||
Dht::put(certId, Value {
|
||||
dht_->put(certId, Value {
|
||||
CERTIFICATE_TYPE,
|
||||
*certificate_,
|
||||
1
|
||||
@@ -76,7 +67,7 @@ SecureDht::~SecureDht()
|
||||
ValueType
|
||||
SecureDht::secureType(ValueType&& type)
|
||||
{
|
||||
type.storePolicy = [this,type](InfoHash id, Sp<Value>& v, InfoHash nid, const sockaddr* a, socklen_t al) {
|
||||
type.storePolicy = [this,type](InfoHash id, Sp<Value>& v, const InfoHash& nid, const SockAddr& a) {
|
||||
if (v->isSigned()) {
|
||||
if (!v->owner or !v->owner->checkSignature(v->getToSign(), v->signature)) {
|
||||
DHT_LOG.WARN("Signature verification failed");
|
||||
@@ -85,11 +76,11 @@ SecureDht::secureType(ValueType&& type)
|
||||
else
|
||||
DHT_LOG.WARN("Signature verification succeeded");
|
||||
}
|
||||
return type.storePolicy(id, v, nid, a, al);
|
||||
return type.storePolicy(id, v, nid, a);
|
||||
};
|
||||
type.editPolicy = [this,type](InfoHash id, const Sp<Value>& o, Sp<Value>& n, InfoHash nid, const sockaddr* a, socklen_t al) {
|
||||
type.editPolicy = [this,type](InfoHash id, const Sp<Value>& o, Sp<Value>& n, const InfoHash& nid, const SockAddr& a) {
|
||||
if (!o->isSigned())
|
||||
return type.editPolicy(id, o, n, nid, a, al);
|
||||
return type.editPolicy(id, o, n, nid, a);
|
||||
if (o->owner != n->owner) {
|
||||
DHT_LOG.WARN("Edition forbidden: owner changed.");
|
||||
return false;
|
||||
@@ -190,7 +181,7 @@ SecureDht::findCertificate(const InfoHash& node, std::function<void(const Sp<cry
|
||||
}
|
||||
|
||||
auto found = std::make_shared<bool>(false);
|
||||
Dht::get(node, [cb,node,found,this](const std::vector<Sp<Value>>& vals) {
|
||||
dht_->get(node, [cb,node,found,this](const std::vector<Sp<Value>>& vals) {
|
||||
if (*found)
|
||||
return false;
|
||||
for (const auto& v : vals) {
|
||||
@@ -238,8 +229,13 @@ SecureDht::getCallbackFilter(GetCallback cb, Value::Filter&& filter)
|
||||
for (const auto& v : values) {
|
||||
// Decrypt encrypted values
|
||||
if (v->isEncrypted()) {
|
||||
if (not key_)
|
||||
if (not key_) {
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
if (forward_all_) // We are currently a proxy, send messages to clients.
|
||||
tmpvals.push_back(v);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Value decrypted_val (decrypt(*v));
|
||||
if (decrypted_val.recipient == getId()) {
|
||||
@@ -277,13 +273,13 @@ SecureDht::getCallbackFilter(GetCallback cb, Value::Filter&& filter)
|
||||
void
|
||||
SecureDht::get(const InfoHash& id, GetCallback cb, DoneCallback donecb, Value::Filter&& f, Where&& w)
|
||||
{
|
||||
Dht::get(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), donecb, {}, std::forward<Where>(w));
|
||||
dht_->get(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), donecb, {}, std::forward<Where>(w));
|
||||
}
|
||||
|
||||
size_t
|
||||
SecureDht::listen(const InfoHash& id, GetCallback cb, Value::Filter&& f, Where&& w)
|
||||
SecureDht::listen(const InfoHash& id, GetCallback cb, Value::Filter f, Where w)
|
||||
{
|
||||
return Dht::listen(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), {}, std::forward<Where>(w));
|
||||
return dht_->listen(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), {}, std::forward<Where>(w));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -295,7 +291,7 @@ SecureDht::putSigned(const InfoHash& hash, Sp<Value> val, DoneCallback callback,
|
||||
}
|
||||
|
||||
// Check if we are already announcing a value
|
||||
auto p = getPut(hash, val->id);
|
||||
auto p = dht_->getPut(hash, val->id);
|
||||
if (p && val->seq <= p->seq) {
|
||||
DHT_LOG.DEBUG("Found previous value being announced.");
|
||||
val->seq = p->seq + 1;
|
||||
@@ -317,7 +313,7 @@ SecureDht::putSigned(const InfoHash& hash, Sp<Value> val, DoneCallback callback,
|
||||
},
|
||||
[hash,val,this,callback,permanent] (bool /* ok */) {
|
||||
sign(*val);
|
||||
put(hash, val, callback, time_point::max(), permanent);
|
||||
dht_->put(hash, val, callback, time_point::max(), permanent);
|
||||
},
|
||||
Value::IdFilter(val->id)
|
||||
);
|
||||
@@ -334,7 +330,7 @@ SecureDht::putEncrypted(const InfoHash& hash, const InfoHash& to, Sp<Value> val,
|
||||
}
|
||||
DHT_LOG.WARN("Encrypting data for PK: %s", pk->getId().toString().c_str());
|
||||
try {
|
||||
put(hash, encrypt(*val, *pk), callback, time_point::max(), permanent);
|
||||
dht_->put(hash, encrypt(*val, *pk), callback, time_point::max(), permanent);
|
||||
} catch (const std::exception& e) {
|
||||
DHT_LOG.ERR("Error putting encrypted data: %s", e.what());
|
||||
if (callback)
|
||||
|
||||
+59
-10
@@ -27,6 +27,33 @@
|
||||
|
||||
namespace dht {
|
||||
|
||||
static constexpr std::array<uint8_t, 12> MAPPED_IPV4_PREFIX {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}};
|
||||
|
||||
std::vector<SockAddr>
|
||||
SockAddr::resolve(const std::string& host, const std::string& service)
|
||||
{
|
||||
std::vector<SockAddr> ips {};
|
||||
if (host.empty())
|
||||
return ips;
|
||||
|
||||
addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
addrinfo* info = nullptr;
|
||||
int rc = getaddrinfo(host.c_str(), service.empty() ? nullptr : service.c_str(), &hints, &info);
|
||||
if(rc != 0)
|
||||
throw std::invalid_argument(std::string("Error: `") + host + ":" + service + "`: " + gai_strerror(rc));
|
||||
|
||||
addrinfo* infop = info;
|
||||
while (infop) {
|
||||
ips.emplace_back(infop->ai_addr, infop->ai_addrlen);
|
||||
infop = infop->ai_next;
|
||||
}
|
||||
freeaddrinfo(info);
|
||||
return ips;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
print_addr(const sockaddr* sa, socklen_t slen)
|
||||
{
|
||||
@@ -38,7 +65,7 @@ print_addr(const sockaddr* sa, socklen_t slen)
|
||||
out << "[" << hbuf << "]";
|
||||
else
|
||||
out << hbuf;
|
||||
if (strcmp(sbuf, "0"))
|
||||
if (std::strcmp(sbuf, "0"))
|
||||
out << ":" << sbuf;
|
||||
} else
|
||||
out << "[invalid address]";
|
||||
@@ -51,11 +78,6 @@ print_addr(const sockaddr_storage& ss, socklen_t sslen)
|
||||
return print_addr((const sockaddr*)&ss, sslen);
|
||||
}
|
||||
|
||||
std::string
|
||||
printAddr(const SockAddr& addr) {
|
||||
return print_addr((const sockaddr*)&addr.first, addr.second);
|
||||
}
|
||||
|
||||
bool
|
||||
SockAddr::isUnspecified() const
|
||||
{
|
||||
@@ -74,7 +96,8 @@ SockAddr::isLoopback() const
|
||||
{
|
||||
switch (getFamily()) {
|
||||
case AF_INET: {
|
||||
uint8_t b1 = (uint8_t)(getIPv4().sin_addr.s_addr >> 24);
|
||||
auto addr_host = ntohl(getIPv4().sin_addr.s_addr);
|
||||
uint8_t b1 = (uint8_t)(addr_host >> 24);
|
||||
return b1 == 127;
|
||||
}
|
||||
case AF_INET6:
|
||||
@@ -91,10 +114,11 @@ SockAddr::isPrivate() const
|
||||
return true;
|
||||
}
|
||||
switch (getFamily()) {
|
||||
case AF_INET:
|
||||
case AF_INET: {
|
||||
auto addr_host = ntohl(getIPv4().sin_addr.s_addr);
|
||||
uint8_t b1, b2;
|
||||
b1 = (uint8_t)(getIPv4().sin_addr.s_addr >> 24);
|
||||
b2 = (uint8_t)((getIPv4().sin_addr.s_addr >> 16) & 0x0ff);
|
||||
b1 = (uint8_t)(addr_host >> 24);
|
||||
b2 = (uint8_t)((addr_host >> 16) & 0x0ff);
|
||||
// 10.x.y.z
|
||||
if (b1 == 10)
|
||||
return true;
|
||||
@@ -105,6 +129,7 @@ SockAddr::isPrivate() const
|
||||
if ((b1 == 192) && (b2 == 168))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
case AF_INET6: {
|
||||
const uint8_t* addr6 = reinterpret_cast<const uint8_t*>(&getIPv6().sin6_addr);
|
||||
if (addr6[0] == 0xfc)
|
||||
@@ -116,6 +141,30 @@ SockAddr::isPrivate() const
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SockAddr::isMappedIPv4() const
|
||||
{
|
||||
if (getFamily() != AF_INET6)
|
||||
return false;
|
||||
const uint8_t* addr6 = reinterpret_cast<const uint8_t*>(&getIPv6().sin6_addr);
|
||||
return std::equal(MAPPED_IPV4_PREFIX.begin(), MAPPED_IPV4_PREFIX.end(), addr6);
|
||||
}
|
||||
|
||||
SockAddr
|
||||
SockAddr::getMappedIPv4() const
|
||||
{
|
||||
if (not isMappedIPv4())
|
||||
return *this;
|
||||
SockAddr ret;
|
||||
ret.setFamily(AF_INET);
|
||||
ret.setPort(getPort());
|
||||
auto addr6 = reinterpret_cast<const uint8_t*>(&getIPv6().sin6_addr);
|
||||
auto addr4 = reinterpret_cast<uint8_t*>(&ret.getIPv4().sin_addr);
|
||||
addr6 += MAPPED_IPV4_PREFIX.size();
|
||||
std::copy_n(addr6, sizeof(in_addr), addr4);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool operator==(const SockAddr& a, const SockAddr& b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
+81
-7
@@ -22,6 +22,11 @@
|
||||
#include "default_types.h"
|
||||
#include "securedht.h" // print certificate ID
|
||||
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
#include "base64.h"
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
|
||||
namespace dht {
|
||||
|
||||
const std::string Query::QUERY_PARSE_ERROR {"Error parsing query."};
|
||||
@@ -35,12 +40,13 @@ Value::Filter bindFilterRaw(FilterRaw raw_filter, void* user_data) {
|
||||
|
||||
std::ostream& operator<< (std::ostream& s, const Value& v)
|
||||
{
|
||||
auto flags(s.flags());
|
||||
s << "Value[id:" << std::hex << v.id << std::dec << " ";
|
||||
if (v.isEncrypted())
|
||||
s << "encrypted ";
|
||||
else if (v.isSigned()) {
|
||||
s << "signed (v" << v.seq << ") ";
|
||||
if (v.recipient != InfoHash())
|
||||
if (v.recipient)
|
||||
s << "decrypted ";
|
||||
}
|
||||
if (not v.isEncrypted()) {
|
||||
@@ -63,13 +69,14 @@ std::ostream& operator<< (std::ostream& s, const Value& v)
|
||||
}
|
||||
}
|
||||
s << "]";
|
||||
s.flags(flags);
|
||||
return s;
|
||||
}
|
||||
|
||||
const ValueType ValueType::USER_DATA = {0, "User Data"};
|
||||
|
||||
bool
|
||||
ValueType::DEFAULT_STORE_POLICY(InfoHash, std::shared_ptr<Value>& v, InfoHash, const sockaddr*, socklen_t)
|
||||
ValueType::DEFAULT_STORE_POLICY(InfoHash, std::shared_ptr<Value>& v, const InfoHash&, const SockAddr&)
|
||||
{
|
||||
return v->size() <= MAX_VALUE_SIZE;
|
||||
}
|
||||
@@ -165,6 +172,71 @@ Value::msgpack_unpack_body(const msgpack::object& o)
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
|
||||
Value::Value(Json::Value& json)
|
||||
{
|
||||
try {
|
||||
if (json.isMember("id"))
|
||||
id = ValueType::Id(json["id"].asInt());
|
||||
} catch (...) { }
|
||||
if (json.isMember("cypher")) {
|
||||
auto cypherStr = json["cypher"].asString();
|
||||
cypherStr = base64_decode(cypherStr);
|
||||
cypher = std::vector<unsigned char>(cypherStr.begin(), cypherStr.end());
|
||||
}
|
||||
if (json.isMember("sig")) {
|
||||
auto sigStr = json["sig"].asString();
|
||||
sigStr = base64_decode(sigStr);
|
||||
signature = std::vector<unsigned char>(sigStr.begin(), sigStr.end());
|
||||
}
|
||||
if (json.isMember("seq"))
|
||||
seq = json["seq"].asInt();
|
||||
if (json.isMember("owner")) {
|
||||
auto ownerStr = json["owner"].asString();
|
||||
auto ownerBlob = std::vector<unsigned char>(ownerStr.begin(), ownerStr.end());
|
||||
owner = std::make_shared<const crypto::PublicKey>(ownerBlob);
|
||||
}
|
||||
if (json.isMember("to")) {
|
||||
auto toStr = json["to"].asString();
|
||||
recipient = InfoHash(toStr);
|
||||
}
|
||||
if (json.isMember("type"))
|
||||
type = json["type"].asInt();
|
||||
if (json.isMember("data")){
|
||||
auto dataStr = json["data"].asString();
|
||||
dataStr = base64_decode(dataStr);
|
||||
data = std::vector<unsigned char>(dataStr.begin(), dataStr.end());
|
||||
}
|
||||
if (json.isMember("utype"))
|
||||
user_type = json["utype"].asString();
|
||||
}
|
||||
|
||||
Json::Value
|
||||
Value::toJson() const
|
||||
{
|
||||
Json::Value val;
|
||||
val["id"] = std::to_string(id);
|
||||
if (isEncrypted()) {
|
||||
val["cypher"] = base64_encode(cypher);
|
||||
} else {
|
||||
if (isSigned())
|
||||
val["sig"] = base64_encode(signature);
|
||||
bool has_owner = owner && *owner;
|
||||
if (has_owner) { // isSigned
|
||||
val["seq"] = seq;
|
||||
val["owner"] = owner->toString();
|
||||
if (recipient != InfoHash())
|
||||
val["to"] = recipient.toString();
|
||||
}
|
||||
val["type"] = type;
|
||||
val["data"] = base64_encode(data);
|
||||
if (not user_type.empty())
|
||||
val["utype"] = user_type;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
bool
|
||||
FieldValue::operator==(const FieldValue& vfd) const
|
||||
{
|
||||
@@ -257,9 +329,12 @@ std::ostream& operator<<(std::ostream& os, const FieldValueIndex& fvi) {
|
||||
os << "Index[";
|
||||
for (auto v = fvi.index.begin(); v != fvi.index.end(); ++v) {
|
||||
switch (v->first) {
|
||||
case Value::Field::Id:
|
||||
case Value::Field::Id: {
|
||||
auto flags(os.flags());
|
||||
os << "Id:" << std::hex << v->second.getInt();
|
||||
os.flags(flags);
|
||||
break;
|
||||
}
|
||||
case Value::Field::ValueType:
|
||||
os << "ValueType:" << v->second.getInt();
|
||||
break;
|
||||
@@ -360,10 +435,10 @@ Where::Where(const std::string& q_str) {
|
||||
std::string s {};
|
||||
std::istringstream convert {value_str};
|
||||
convert >> v;
|
||||
if (convert.failbit
|
||||
if (not convert
|
||||
and value_str.size() > 1
|
||||
and value_str[0] == '\"'
|
||||
and value_str[value_str.size()-1] == '\"')
|
||||
and value_str[0] == '"'
|
||||
and value_str[value_str.size()-1] == '"')
|
||||
s = value_str.substr(1, value_str.size()-2);
|
||||
else
|
||||
s = value_str;
|
||||
@@ -490,4 +565,3 @@ std::ostream& operator<<(std::ostream& s, const dht::Where& where) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
+9
-4
@@ -45,7 +45,7 @@ void print_node_info(const DhtRunner& dht, const dht_params&) {
|
||||
void print_usage() {
|
||||
std::cout << "Usage: dhtchat [-n network_id] [-p local_port] [-b bootstrap_host[:port]]" << std::endl << std::endl;
|
||||
std::cout << "dhtchat, a simple OpenDHT command line chat client." << std::endl;
|
||||
std::cout << "Report bugs to: http://opendht.net" << std::endl;
|
||||
std::cout << "Report bugs to: https://opendht.net" << std::endl;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -67,6 +67,13 @@ main(int argc, char **argv)
|
||||
if (not params.bootstrap.first.empty())
|
||||
dht.bootstrap(params.bootstrap.first.c_str(), params.bootstrap.second.c_str());
|
||||
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
if (!params.proxyclient.empty()) {
|
||||
dht.setProxyServer(params.proxyclient);
|
||||
dht.enableProxy(true);
|
||||
}
|
||||
#endif //OPENDHT_PROXY_CLIENT
|
||||
|
||||
print_node_info(dht, params);
|
||||
std::cout << " type 'c {hash}' to join a channel" << std::endl << std::endl;
|
||||
|
||||
@@ -88,8 +95,6 @@ main(int argc, char **argv)
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
static constexpr dht::InfoHash INVALID_ID {};
|
||||
|
||||
std::istringstream iss(line);
|
||||
std::string op, idstr;
|
||||
iss >> op;
|
||||
@@ -99,7 +104,7 @@ main(int argc, char **argv)
|
||||
else if (op == "c") {
|
||||
iss >> idstr;
|
||||
room = InfoHash(idstr);
|
||||
if (room == INVALID_ID) {
|
||||
if (not room) {
|
||||
room = InfoHash::get(idstr);
|
||||
std::cout << "Joining h(" << idstr << ") = " << room << std::endl;
|
||||
}
|
||||
|
||||
+171
-29
@@ -1,8 +1,9 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
|
||||
* Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -29,9 +30,9 @@ extern "C" {
|
||||
using namespace dht;
|
||||
|
||||
void print_usage() {
|
||||
std::cout << "Usage: dhtnode [-v [-l logfile]] [-i] [-d] [-n network_id] [-p local_port] [-b bootstrap_host[:port]]" << std::endl << std::endl;
|
||||
std::cout << "Usage: dhtnode [-v [-l logfile]] [-i] [-d] [-n network_id] [-p local_port] [-b bootstrap_host[:port]] [--proxyserver local_port]" << std::endl << std::endl;
|
||||
std::cout << "dhtnode, a simple OpenDHT command line node runner." << std::endl;
|
||||
std::cout << "Report bugs to: http://opendht.net" << std::endl;
|
||||
std::cout << "Report bugs to: https://opendht.net" << std::endl;
|
||||
}
|
||||
|
||||
void print_id_req() {
|
||||
@@ -55,15 +56,42 @@ void print_help() {
|
||||
<< " ll Print basic information and stats about the current node." << std::endl
|
||||
<< " ls [key] Print basic information about current search(es)." << std::endl
|
||||
<< " ld [key] Print basic information about currenty stored values on this node (or key)." << std::endl
|
||||
<< " lr Print the full current routing table of this node" << std::endl;
|
||||
<< " lr Print the full current routing table of this node." << std::endl;
|
||||
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
std::cout << std::endl << "Operations with the proxy:" << std::endl
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
<< " pst [port] <pushServer> Start the proxy interface on port." << std::endl
|
||||
#else
|
||||
<< " pst [port] Start the proxy interface on port." << std::endl
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
<< " psp [port] Stop the proxy interface on port." << std::endl;
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
std::cout << std::endl << "Operations with the proxy:" << std::endl
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
<< " stt [server_address] <device_key> Start the proxy client." << std::endl
|
||||
#else
|
||||
<< " stt [server_address] Start the proxy client." << std::endl
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
<< " rs [token] Resubscribe to opendht." << std::endl
|
||||
<< " rp [push_notification] Inject a push notification in Opendht." << std::endl
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
<< " stp Stop the proxy client." << std::endl;
|
||||
#endif //OPENDHT_PROXY_CLIENT
|
||||
|
||||
std::cout << std::endl << "Operations on the DHT:" << std::endl
|
||||
<< " b <ip:port> Ping potential node at given IP address/port." << std::endl
|
||||
<< " b <ip:port> Ping potential node at given IP address/port." << std::endl
|
||||
<< " g <key> Get values at <key>." << std::endl
|
||||
<< " l <key> Listen for value changes at <key>." << std::endl
|
||||
<< " cl <key> <token> Cancel listen for <token> and <key>." << std::endl
|
||||
<< " p <key> <str> Put string value at <key>." << std::endl
|
||||
<< " pp <key> <str> Put string value at <key> (persistent version)." << std::endl
|
||||
<< " s <key> <str> Put string value at <key>, signed with our generated private key." << std::endl
|
||||
<< " e <key> <dest> <str> Put string value at <key>, encrypted for <dest> with its public key (if found)." << std::endl;
|
||||
|
||||
std::cout << std::endl << "Indexation operations on the DHT:" << std::endl
|
||||
<< " il <name> <key> [exact match] Lookup the index named <name> with the key <key>." << std::endl
|
||||
<< " Set [exact match] to 'false' for inexact match lookup." << std::endl
|
||||
@@ -71,7 +99,11 @@ void print_help() {
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
, std::map<in_port_t, std::unique_ptr<DhtProxyServer>>& proxies
|
||||
#endif
|
||||
)
|
||||
{
|
||||
print_node_info(dht, params);
|
||||
std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl << std::endl;
|
||||
@@ -91,7 +123,7 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
break;
|
||||
|
||||
std::istringstream iss(line);
|
||||
std::string op, idstr, value, index, keystr;
|
||||
std::string op, idstr, value, index, keystr, pushServer, deviceKey;
|
||||
iss >> op;
|
||||
|
||||
if (op == "x" || op == "exit" || op == "quit") {
|
||||
@@ -115,20 +147,18 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
} else if (op == "ld") {
|
||||
iss >> idstr;
|
||||
InfoHash filter(idstr);
|
||||
if (filter == InfoHash{})
|
||||
std::cout << dht->getStorageLog() << std::endl;
|
||||
else
|
||||
if (filter)
|
||||
std::cout << dht->getStorageLog(filter) << std::endl;
|
||||
else
|
||||
std::cout << dht->getStorageLog() << std::endl;
|
||||
continue;
|
||||
} else if (op == "ls") {
|
||||
iss >> idstr;
|
||||
InfoHash filter(idstr);
|
||||
if (filter == InfoHash{}) {
|
||||
std::cout << "Searches:" << std::endl;
|
||||
std::cout << dht->getSearchesLog() << std::endl;
|
||||
} else {
|
||||
if (filter)
|
||||
std::cout << dht->getSearchLog(filter) << std::endl;
|
||||
}
|
||||
else
|
||||
std::cout << dht->getSearchesLog() << std::endl;
|
||||
continue;
|
||||
} else if (op == "la") {
|
||||
std::cout << "Reported public addresses:" << std::endl;
|
||||
@@ -161,16 +191,72 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
dht->setLogFilter(filter);
|
||||
continue;
|
||||
}
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
else if (op == "pst") {
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
iss >> idstr >> pushServer;
|
||||
#else
|
||||
iss >> idstr;
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
try {
|
||||
unsigned int port = std::stoi(idstr);
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
proxies.emplace(port, new DhtProxyServer(dht, port, pushServer));
|
||||
#else
|
||||
proxies.emplace(port, new DhtProxyServer(dht, port));
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
} catch (...) { }
|
||||
continue;
|
||||
} else if (op == "psp") {
|
||||
iss >> idstr;
|
||||
try {
|
||||
auto it = proxies.find(std::stoi(idstr));
|
||||
if (it != proxies.end())
|
||||
proxies.erase(it);
|
||||
} catch (...) { }
|
||||
continue;
|
||||
}
|
||||
#endif //OPENDHT_PROXY_SERVER
|
||||
#if OPENDHT_PROXY_CLIENT
|
||||
else if (op == "stt") {
|
||||
/*iss >> idstr >> deviceKey;
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
if (not deviceKey.empty())
|
||||
dht->setPushNotificationToken(deviceKey);
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
dht->setProxyServer(idstr);*/
|
||||
dht->enableProxy(true);
|
||||
continue;
|
||||
} else if (op == "stp") {
|
||||
dht->enableProxy(false);
|
||||
continue;
|
||||
}
|
||||
#if OPENDHT_PUSH_NOTIFICATIONS
|
||||
else if (op == "rp") {
|
||||
iss >> value;
|
||||
dht->pushNotificationReceived({{"token", value}});
|
||||
continue;
|
||||
} else if (op == "re") {
|
||||
iss >> value;
|
||||
try {
|
||||
unsigned token = std::stoul(value);
|
||||
dht->resubscribe(token);
|
||||
} catch (...) { }
|
||||
continue;
|
||||
}
|
||||
#endif // OPENDHT_PUSH_NOTIFICATIONS
|
||||
#endif //OPENDHT_PROXY_CLIENT
|
||||
|
||||
if (op.empty())
|
||||
continue;
|
||||
|
||||
static const std::set<std::string> VALID_OPS {"g", "l", "il", "ii", "p", "s", "e", "a"};
|
||||
static const std::set<std::string> VALID_OPS {"g", "l", "cl", "il", "ii", "p", "pp", "cpp", "s", "e", "a", "q"};
|
||||
if (VALID_OPS.find(op) == VALID_OPS.cend()) {
|
||||
std::cout << "Unknown command: " << op << std::endl;
|
||||
std::cout << " (type 'h' or 'help' for a list of possible commands)" << std::endl;
|
||||
continue;
|
||||
}
|
||||
dht::InfoHash id;
|
||||
|
||||
if (op == "il" or op == "ii") {
|
||||
// Pht syntax
|
||||
@@ -197,12 +283,17 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
else {
|
||||
// Dht syntax
|
||||
iss >> idstr;
|
||||
InfoHash h {idstr};
|
||||
if (not isInfoHash(h))
|
||||
continue;
|
||||
id = dht::InfoHash(idstr);
|
||||
if (not id) {
|
||||
if (idstr.empty()) {
|
||||
std::cerr << "Syntax error: invalid InfoHash." << std::endl;
|
||||
continue;
|
||||
}
|
||||
id = InfoHash::get(idstr);
|
||||
std::cout << "Using h(" << idstr << ") = " << id << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
dht::InfoHash id {idstr};
|
||||
// Dht
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
if (op == "g") {
|
||||
@@ -236,11 +327,18 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
else if (op == "l") {
|
||||
std::string rem;
|
||||
std::getline(iss, rem);
|
||||
dht->listen(id, [](std::shared_ptr<Value> value) {
|
||||
auto token = dht->listen(id, [](std::shared_ptr<Value> value) {
|
||||
std::cout << "Listen: found value:" << std::endl;
|
||||
std::cout << "\t" << *value << std::endl;
|
||||
return true;
|
||||
}, {}, dht::Where {std::move(rem)});
|
||||
auto t = token.get();
|
||||
std::cout << "Listening, token: " << t << std::endl;
|
||||
}
|
||||
else if (op == "cl") {
|
||||
std::string rem;
|
||||
iss >> rem;
|
||||
dht->cancelListen(id, std::stoul(rem));
|
||||
}
|
||||
else if (op == "p") {
|
||||
std::string v;
|
||||
@@ -253,6 +351,25 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
|
||||
std::cout << "Put: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
|
||||
});
|
||||
}
|
||||
else if (op == "pp") {
|
||||
std::string v;
|
||||
iss >> v;
|
||||
auto value = std::make_shared<dht::Value>(
|
||||
dht::ValueType::USER_DATA.id,
|
||||
std::vector<uint8_t> {v.begin(), v.end()}
|
||||
);
|
||||
dht->put(id, value, [start,value](bool ok) {
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto flags(std::cout.flags());
|
||||
std::cout << "Put: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s). Value ID: " << std::hex << value->id << std::endl;
|
||||
std::cout.flags(flags);
|
||||
}, time_point::max(), true);
|
||||
}
|
||||
else if (op == "cpp") {
|
||||
std::string rem;
|
||||
iss >> rem;
|
||||
dht->cancelPut(id, std::stoul(rem, nullptr, 16));
|
||||
}
|
||||
else if (op == "s") {
|
||||
if (not params.generate_identity) {
|
||||
print_id_req();
|
||||
@@ -357,6 +474,7 @@ main(int argc, char **argv)
|
||||
print_usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (params.daemonize) {
|
||||
daemonize();
|
||||
} else if (params.service) {
|
||||
@@ -365,11 +483,20 @@ main(int argc, char **argv)
|
||||
|
||||
dht::crypto::Identity crt {};
|
||||
if (params.generate_identity) {
|
||||
auto ca_tmp = dht::crypto::generateIdentity("DHT Node CA");
|
||||
crt = dht::crypto::generateIdentity("DHT Node", ca_tmp);
|
||||
auto ca_tmp = dht::crypto::generateEcIdentity("DHT Node CA");
|
||||
crt = dht::crypto::generateEcIdentity("DHT Node", ca_tmp);
|
||||
}
|
||||
|
||||
dht->run(params.port, crt, true, params.network);
|
||||
dht::DhtRunner::Config config;
|
||||
config.dht_config.node_config.network = params.network;
|
||||
config.dht_config.id = crt;
|
||||
config.threaded = true;
|
||||
config.proxy_server = params.proxyclient;
|
||||
config.push_node_id = "dhtnode";
|
||||
if (not params.proxyclient.empty())
|
||||
dht->setPushNotificationToken(params.devicekey);
|
||||
|
||||
dht->run(params.port, config);
|
||||
|
||||
if (params.log) {
|
||||
if (params.syslog or (params.daemonize and params.logfile.empty()))
|
||||
@@ -385,12 +512,27 @@ main(int argc, char **argv)
|
||||
dht->bootstrap(params.bootstrap.first.c_str(), params.bootstrap.second.c_str());
|
||||
}
|
||||
|
||||
if (params.daemonize or params.service) {
|
||||
while (runner.wait());
|
||||
} else {
|
||||
cmd_loop(dht, params);
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
std::map<in_port_t, std::unique_ptr<DhtProxyServer>> proxies;
|
||||
#endif
|
||||
if (params.proxyserver != 0) {
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
proxies.emplace(params.proxyserver, new DhtProxyServer(dht, params.proxyserver, params.pushserver));
|
||||
#else
|
||||
std::cerr << "DHT proxy server requested but OpenDHT built without proxy server support." << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (params.daemonize or params.service)
|
||||
while (runner.wait());
|
||||
else
|
||||
cmd_loop(dht, params
|
||||
#if OPENDHT_PROXY_SERVER
|
||||
, proxies
|
||||
#endif
|
||||
);
|
||||
|
||||
} catch(const std::exception&e) {
|
||||
std::cerr << std::endl << e.what() << std::endl;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ using namespace dht;
|
||||
void print_usage() {
|
||||
std::cout << "Usage: dhtscanner [-n network_id] [-p local_port] [-b bootstrap_host[:port]]" << std::endl << std::endl;
|
||||
std::cout << "dhtscanner, a simple OpenDHT command line utility generating scan result the network." << std::endl;
|
||||
std::cout << "Report bugs to: http://opendht.net" << std::endl;
|
||||
std::cout << "Report bugs to: https://opendht.net" << std::endl;
|
||||
}
|
||||
|
||||
struct snode_compare {
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>OpenDHT tester</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript">"use strict";
|
||||
let onGet;
|
||||
let onPut;
|
||||
let setServer;
|
||||
const valueGetElement = function(o) {
|
||||
const d = window.atob(o.data);
|
||||
return d;
|
||||
};
|
||||
$(function() {
|
||||
let request = undefined;
|
||||
let server;
|
||||
const getTools = $("#getTools");
|
||||
const getBtn = $("#getBtn");
|
||||
const getDropdown = $("#getDropdown");
|
||||
const listenBtn = $("#listenBtn").click(function(){onGet('LISTEN');});
|
||||
const setGetRequest = function() {
|
||||
getBtn.button('loading');
|
||||
getStopBtn.appendTo(getTools);
|
||||
getDropdown.hide();
|
||||
}
|
||||
const clearGetRequest = function() {
|
||||
if (request === undefined)
|
||||
return;
|
||||
request.abort();
|
||||
request = undefined;
|
||||
getStopBtn.detach();
|
||||
getDropdown.show();
|
||||
getBtn.button('reset');
|
||||
}
|
||||
const getStopBtn = $("#getStopBtn").detach().click(clearGetRequest);
|
||||
const putBtn = $("#putBtn");
|
||||
const result = $("#dhtResult");
|
||||
const group = $('<ul class="list-group"/>').appendTo(result);
|
||||
onGet = function (method) {
|
||||
if (request !== undefined)
|
||||
return false;
|
||||
const input = $("#getKey").val();
|
||||
group.empty();
|
||||
let lastAppended = 0;
|
||||
let start = new Date().getTime();
|
||||
request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function(event) {
|
||||
if (this.readyState >= XMLHttpRequest.LOADING) {
|
||||
if (this.readyState == XMLHttpRequest.DONE) {
|
||||
clearGetRequest();
|
||||
}
|
||||
if (this.status === 200) {
|
||||
const elements = this.responseText.split("\n");
|
||||
const elementsLength = elements.length;
|
||||
const now = new Date().getTime();
|
||||
for (let i = lastAppended; i < elementsLength; i++) {
|
||||
const element = elements[i];
|
||||
if (!element || element.length == 0)
|
||||
return;
|
||||
const o = JSON.parse(element);
|
||||
const d = window.atob(o.data);
|
||||
const delay = Math.max(0, start-now);
|
||||
$('<li class="list-group-item"/>').append(valueGetElement(o)).appendTo(group).hide().delay(delay).slideDown(100);
|
||||
lastAppended = i+1;
|
||||
start = Math.max(start, now)+25;
|
||||
}
|
||||
} else if (this.status !== 0) {
|
||||
group.empty().append($('<li class="list-group-item list-group-item-danger"/>').text("Error loading content: " + this.statusText));
|
||||
}
|
||||
}
|
||||
};
|
||||
request.onerror = function(event) {
|
||||
clearGetRequest();
|
||||
group.empty().append($('<li class="list-group-item list-group-item-danger"/>').text("Error loading content."));
|
||||
};
|
||||
request.open(method, server + input, true);
|
||||
request.send(null);
|
||||
setGetRequest();
|
||||
return false;
|
||||
};
|
||||
|
||||
onPut = function( ) {
|
||||
const key = $("#getKey").val();
|
||||
const value = $("#putValue").val();
|
||||
$.ajax({
|
||||
url: server + key,
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
data:window.btoa(value)
|
||||
}),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
success: function( result ) {
|
||||
putBtn.button('reset');
|
||||
//$('<li class="list-group-item list-group-item-success"/>').append(valueGetElement(result)).appendTo(group.empty());
|
||||
},
|
||||
error: function(result) {
|
||||
putBtn.button('reset');
|
||||
group.empty().append($('<li class="list-group-item list-group-item-danger"/>').text(result.statusText));
|
||||
}
|
||||
});
|
||||
putBtn.button('loading');
|
||||
return false;
|
||||
};
|
||||
|
||||
const serverValue = $("#serverValue");
|
||||
const serverStatus = $("#serverStatus");
|
||||
const serverBtn = $("#serverBtn");
|
||||
setServer = function(event) {
|
||||
server = 'http://'+serverValue.val() + '/';
|
||||
serverStatus.empty();
|
||||
serverBtn.button('loading');
|
||||
$.getJSON(server, function(data){
|
||||
$('<span><b>Node</b> '+data.node_id+'</span>').appendTo(serverStatus).hide().fadeIn();
|
||||
}).fail(function(error) {
|
||||
serverStatus.html("<div class='alert alert-danger' style='margin-bottom: 0px;'><span class='glyphicon glyphicon-remove' aria-hidden='true'></span> Can't access node</div>");
|
||||
}).always(function(error) {
|
||||
serverBtn.button('reset');
|
||||
});
|
||||
return false;
|
||||
};
|
||||
setServer();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" style="max-width: 730px;">
|
||||
<header class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
<h1>OpenDHT tester</h1>
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="well well-sm" style="margin-top:10px; margin-bottom:0px;">
|
||||
<form id="serverForm" class="form-inline" onsubmit="return setServer();" style="margin-bottom:4px;">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="serverValue" placeholder="Proxy server" value="127.0.0.1:8080"/>
|
||||
<span class="input-group-btn">
|
||||
<button id="serverBtn" type="submit" class="btn btn-default" data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i>"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<div id="serverStatus"><i class='fa fa-circle-o-notch fa-spin'></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="panel panel-default" id="dhtResult">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<form class="form-inline" onsubmit="return onGet('GET');">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="getKey" placeholder="Key" aria-label="Key" />
|
||||
<span class="input-group-btn" id="getTools">
|
||||
<button id="getBtn" class="btn btn-default" data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i>" type="submit">Get</button>
|
||||
<button id="getDropdown"type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> </button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a id="listenBtn" href="#">Listen</a></li>
|
||||
</ul>
|
||||
<button id="getStopBtn" class="btn btn-default" type="submit"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<form class="form-inline" onsubmit="return onPut();">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-group-input" id="putValue" placeholder="Value" />
|
||||
<span class="input-group-btn">
|
||||
<button id="putBtn" type="submit" class="btn btn-default" data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Loading">Put</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+28
-4
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -98,10 +99,8 @@ dht::indexation::Pht::Key createPhtKey(std::map<std::string, std::string> pht_ke
|
||||
return pht_key;
|
||||
}
|
||||
|
||||
bool isInfoHash(dht::InfoHash& h) {
|
||||
static constexpr dht::InfoHash INVALID_ID {};
|
||||
|
||||
if (h == INVALID_ID) {
|
||||
bool isInfoHash(const dht::InfoHash& h) {
|
||||
if (not h) {
|
||||
std::cout << "Syntax error: invalid InfoHash." << std::endl;
|
||||
return false;
|
||||
}
|
||||
@@ -121,6 +120,10 @@ struct dht_params {
|
||||
bool daemonize {false};
|
||||
bool service {false};
|
||||
std::pair<std::string, std::string> bootstrap {};
|
||||
in_port_t proxyserver {0};
|
||||
std::string proxyclient {};
|
||||
std::string pushserver {};
|
||||
std::string devicekey {};
|
||||
};
|
||||
|
||||
static const constexpr struct option long_options[] = {
|
||||
@@ -134,6 +137,10 @@ static const constexpr struct option long_options[] = {
|
||||
{"service", no_argument , nullptr, 's'},
|
||||
{"logfile", required_argument, nullptr, 'l'},
|
||||
{"syslog", no_argument , nullptr, 'L'},
|
||||
{"proxyserver",required_argument, nullptr, 'S'},
|
||||
{"proxyclient",required_argument, nullptr, 'C'},
|
||||
{"pushserver", required_argument, nullptr, 'P'},
|
||||
{"devicekey", required_argument, nullptr, 'D'},
|
||||
{nullptr, 0 , nullptr, 0}
|
||||
};
|
||||
|
||||
@@ -151,6 +158,23 @@ parseArgs(int argc, char **argv) {
|
||||
std::cout << "Invalid port: " << port_arg << std::endl;
|
||||
}
|
||||
break;
|
||||
case 'S': {
|
||||
int port_arg = atoi(optarg);
|
||||
if (port_arg >= 0 && port_arg < 0x10000)
|
||||
params.proxyserver = port_arg;
|
||||
else
|
||||
std::cout << "Invalid port: " << port_arg << std::endl;
|
||||
}
|
||||
break;
|
||||
case 'P':
|
||||
params.pushserver = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
params.proxyclient = optarg;
|
||||
break;
|
||||
case 'D':
|
||||
params.devicekey = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
params.network = strtoul(optarg, nullptr, 0);
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user