diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f6e3af55..56300fbf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ option (OPENDHT_PROXY_CLIENT "Enable DHT proxy client, use Restinio and jsoncpp" option (OPENDHT_PROXY_OPENSSL "Build DHT proxy with OpenSSL" ON) option (OPENDHT_PROXY_HTTP_PARSER_FORK "Build DHT proxy with custom http_parser to support old API" OFF) option (OPENDHT_PEER_DISCOVERY "Enable multicast peer discovery" ON) +option (OPENDHT_CONNECTIVITY_STAT "Enable connectivity stat module" OFF) option (OPENDHT_INDEX "Build DHT indexation feature" OFF) option (OPENDHT_TESTS "Add unit tests executable" OFF) option (OPENDHT_C "Build C bindings" OFF) @@ -91,6 +92,9 @@ endif() if (OPENDHT_PROXY_SERVER OR OPENDHT_PROXY_CLIENT OR OPENDHT_PEER_DISCOVERY) add_definitions(-DASIO_STANDALONE) endif() +if (OPENDHT_CONNECTIVITY_STAT) + find_package (NL REQUIRED) +endif() # Build flags set (CMAKE_CXX_STANDARD 14) @@ -146,6 +150,10 @@ include_directories ( include/opendht/ ${CMAKE_CURRENT_BINARY_DIR}/include/ ) +if (OPENDHT_CONNECTIVITY_STAT) + include_directories (SYSTEM "${NL_INCLUDE_DIRS}") + link_directories (${NL_LIBRARIES}) +endif() # Install dirs include (GNUInstallDirs) @@ -282,6 +290,12 @@ if(OPENDHT_ARGON2) include_directories(argon2/include/) endif() +if (OPENDHT_CONNECTIVITY_STAT) + list (APPEND opendht_SOURCES src/connstat.cpp) + list (APPEND opendht_HEADERS include/opendht/connstat.h) + add_definitions(-DOPENDHT_CONNECTIVITY_STAT) +endif() + # Targets if (OPENDHT_STATIC) add_library (opendht-static STATIC @@ -298,7 +312,7 @@ if (OPENDHT_STATIC) PRIVATE ${argon2_LIBRARIES} PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Jsoncpp_LIBRARIES} ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY} - ${OPENSSL_LIBRARIES}) + ${OPENSSL_LIBRARIES} ${NL_LIBRARIES}) install (TARGETS opendht-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht) endif () @@ -316,11 +330,12 @@ if (OPENDHT_SHARED) target_link_libraries(opendht PRIVATE ${argon2_LIBRARIES}) target_include_directories(opendht SYSTEM PRIVATE ${argon2_INCLUDE_DIRS}) endif () + # FIXME target_link_libraries(opendht PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} PRIVATE ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Jsoncpp_LIBRARIES} - ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY}) + ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY} ${NL_LIBRARIES}) install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht) endif () diff --git a/cmake/FindNL.cmake b/cmake/FindNL.cmake new file mode 100644 index 000000000..dbc7cbede --- /dev/null +++ b/cmake/FindNL.cmake @@ -0,0 +1,140 @@ +# +# Find the native netlink includes and library +# +# If they exist, differentiate between versions 1, 2 and 3. +# Version 1 does not have netlink/version.h +# Version 2 started separating libraries (libnl{,-genl,-route}). +# Version 3 (>= 3.2) started appending the major version number as suffix to +# library names (libnl-3) +# +# NL_INCLUDE_DIRS - where to find libnl.h, etc. +# NL_LIBRARIES - List of libraries when using libnl. +# NL_FOUND - True if libnl found. + +if(NL_LIBRARIES AND NL_INCLUDE_DIRS) + # in cache already + SET(NL_FOUND TRUE) +else() + SET( SEARCHPATHS + /opt/local + /sw + /usr + /usr/local + ) + + find_package(PkgConfig) + pkg_check_modules(NL3 libnl-3.0 libnl-genl-3.0 libnl-route-3.0) + if(NOT NL3_FOUND) + pkg_search_module(NL2 libnl-2.0) + endif() + + # Try to find NL 2.0, 3.0 or 3.1 (/usr/include/netlink/version.h) or + # NL >= 3.2 (/usr/include/libnl3/netlink/version.h) + find_path(NL3_INCLUDE_DIR + PATH_SUFFIXES + include/libnl3 + include + NAMES + netlink/version.h + HINTS + "${NL3_libnl-3.0_INCLUDEDIR}" + "${NL2_INCLUDEDIR}" + PATHS + $(SEARCHPATHS) + ) + # NL version >= 2 + if(NL3_INCLUDE_DIR) + find_library(NL3_LIBRARY + NAMES + nl-3 nl + PATH_SUFFIXES + lib64 lib + HINTS + "${NL3_libnl-3.0_LIBDIR}" + "${NL2_LIBDIR}" + PATHS + $(SEARCHPATHS) + ) + find_library(NLGENL_LIBRARY + NAMES + nl-genl-3 nl-genl + PATH_SUFFIXES + lib64 lib + HINTS + "${NL3_libnl-genl-3.0_LIBDIR}" + "${NL2_LIBDIR}" + PATHS + $(SEARCHPATHS) + ) + find_library(NLROUTE_LIBRARY + NAMES + nl-route-3 nl-route + PATH_SUFFIXES + lib64 lib + HINTS + "${NL3_libnl-route-3.0_LIBDIR}" + "${NL2_LIBDIR}" + PATHS + $(SEARCHPATHS) + ) + # + # If we don't have all of those libraries, we can't use libnl. + # + if(NL3_LIBRARY AND NLGENL_LIBRARY AND NLROUTE_LIBRARY) + set(NL_LIBRARY ${NL3_LIBRARY}) + # NL2 and NL3 are similar and just affect how the version is reported in + # the --version output. In cast of doubt, assume NL3 since a library + # without version number could be any of 2.0, 3.0 or 3.1. + if(NOT NL3_FOUND AND NL2_FOUND) + set(HAVE_LIBNL2 1) + else() + set(HAVE_LIBNL3 1) + endif() + endif() + set(NL_INCLUDE_DIR ${NL3_INCLUDE_DIR}) + endif() + + # libnl-2 and libnl-3 not found, try NL version 1 + if(NOT (NL_LIBRARY AND NL_INCLUDE_DIR)) + pkg_search_module(NL1 libnl-1) + find_path(NL1_INCLUDE_DIR + NAMES + netlink/netlink.h + HINTS + "${NL1_INCLUDEDIR}" + PATHS + $(SEARCHPATHS) + ) + find_library(NL1_LIBRARY + NAMES + nl + PATH_SUFFIXES + lib64 lib + HINTS + "${NL1_LIBDIR}" + PATHS + $(SEARCHPATHS) + ) + set(NL_LIBRARY ${NL1_LIBRARY}) + set(NL_INCLUDE_DIR ${NL1_INCLUDE_DIR}) + if(NL1_LIBRARY AND NL1_INCLUDE_DIR) + set(HAVE_LIBNL1 1) + endif() + endif() +endif() + +# handle the QUIETLY and REQUIRED arguments and set NL_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(NL DEFAULT_MSG NL_LIBRARY NL_INCLUDE_DIR) + +IF(NL_FOUND) + set(NL_LIBRARIES ${NLGENL_LIBRARY} ${NLROUTE_LIBRARY} ${NL_LIBRARY}) + set(NL_INCLUDE_DIRS ${NL_INCLUDE_DIR}) + set(HAVE_LIBNL 1) +else() + set(NL_LIBRARIES ) + set(NL_INCLUDE_DIRS) +endif() + +MARK_AS_ADVANCED( NL_LIBRARIES NL_INCLUDE_DIRS ) diff --git a/configure.ac b/configure.ac index 42354ecb9..9108d591c 100644 --- a/configure.ac +++ b/configure.ac @@ -121,6 +121,24 @@ AC_ARG_ENABLE([proxy_server_identity], AS_HELP_STRING([--enable-proxy-server-ide 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) +dnl connectivity status +AC_ARG_ENABLE([connstat], + AS_HELP_STRING([--disable-connstat], + [Disable native connectivity stat reports])) +AS_IF([test "$SYS" == "android"], [have_libnl=yes], + [test "$SYS" == "linux"], [PKG_CHECK_MODULES([NL3], + [libnl-3.0 libnl-genl-3.0 libnl-route-3.0], + [have_libnl=yes], [have_libnl=no])], + [have_libnl=no] +) +dnl assert on other hosts +AS_IF([test "x$enable_connstat" = "xyes" && "x$have_libnl" = "xno"], [ + AC_MSG_ERROR(["libnl is required connectivity stat"])] +) +AM_CONDITIONAL([ENABLE_CONNSTAT], [test "x$have_libnl" == "xyes"]) +AM_COND_IF([ENABLE_CONNSTAT], [AC_DEFINE([ENABLE_CONNSTAT], [1], [Enable connstat])]) + + AC_ARG_ENABLE([tests], AS_HELP_STRING([--enable-tests], [Enable tests]), build_tests=yes, build_tests=no) AM_CONDITIONAL(ENABLE_TESTS, test x$build_tests == xyes) AM_COND_IF([ENABLE_TESTS], [ diff --git a/include/opendht/connstat.h b/include/opendht/connstat.h new file mode 100644 index 000000000..f6726daef --- /dev/null +++ b/include/opendht/connstat.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014-2020 Savoir-faire Linux Inc. + * Author(s) : Paymon + * + * 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 . + */ + +#pragma once + +#include "log.h" + +#include +#include + +struct nl_sock; +struct nl_msg; + +namespace dht { +namespace net { + +class OPENDHT_PUBLIC ConnectivityStatus +{ +public: + enum class Event : long unsigned int { + NONE_EVENT = 0, + ADDR, + ROUTE, + LINK, + NEIGHT, + EVENTS_NEW, + NEWLINK, + NEWROUTE4, + NEWROUTE6, + NEWROUTE, + NEWADDR4, + NEWADDR6, + NEWADDR, + NEWNEIGH, + NEIGHTBL, + IPV4_MROUTE, + IPV6_MROUTE, + IP_MROUTE, + EVENTS_DEL, + DELLINK, + DELROUTE4, + DELROUTE6, + DELROUTE, + DELADDR4, + DELADDR6, + DELADDR, + DELNEIGH + }; + + ConnectivityStatus(); + + using ConnectionEventCb = std::function; + + void setEventListener (ConnectionEventCb ucb, Event); + void removeEventListener (Event); + +private: + + std::map event_cbs = {}; + + using NlPtr = std::unique_ptr; + NlPtr nlsk; + static NlPtr nlsk_init (); + + void nlsk_setup (nl_sock*); + void nl_event_loop_thrd (nl_sock*); + void get_neigh_state (struct nl_msg*); + int nl_event_cb (struct nl_msg*); + void executer (Event); + + + std::unique_ptr logger_; + + std::thread thrd_; + +}; + +} /* namespace net */ +} /* namespace dht */ diff --git a/src/Makefile.am b/src/Makefile.am index 76560aa46..bf01e244f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ lib_LTLIBRARIES = libopendht.la -libopendht_la_CPPFLAGS = @CPPFLAGS@ -I$(top_srcdir)/include/opendht @Argon2_CFLAGS@ @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@ @OpenSSL_CFLAGS@ @Fmt_CFLAGS@ -libopendht_la_LIBADD = @Argon2_LIBS@ @JsonCpp_LIBS@ @GnuTLS_LIBS@ @Nettle_LIBS@ @OpenSSL_LIBS@ @Fmt_LIBS@ +libopendht_la_CPPFLAGS = @CPPFLAGS@ -I$(top_srcdir)/include/opendht @Argon2_CFLAGS@ @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@ @OpenSSL_CFLAGS@ @Fmt_CFLAGS@ @NL3_CFLAGS@ +libopendht_la_LIBADD = @Argon2_LIBS@ @JsonCpp_LIBS@ @GnuTLS_LIBS@ @Nettle_LIBS@ @OpenSSL_LIBS@ @Fmt_LIBS@ @Nettle_LIBS@ @NL3_LIBS@ libopendht_la_LDFLAGS = @LDFLAGS@ @Argon2_LDFLAGS@ -version-number @OPENDHT_MAJOR_VERSION@:@OPENDHT_MINOR_VERSION@:@OPENDHT_PATCH_VERSION@ libopendht_la_SOURCES = \ dht.cpp \ @@ -80,6 +80,11 @@ libopendht_la_SOURCES += peer_discovery.cpp nobase_include_HEADERS += ../include/opendht/peer_discovery.h endif +if ENABLE_CONNSTAT +libopendht_la_SOURCES += connstat.cpp +nobase_include_HEADERS += ../include/opendht/connstat.h +endif + if ENABLE_INDEXATION libopendht_la_SOURCES += indexation/pht.cpp nobase_include_HEADERS += ../include/opendht/indexation/pht.h diff --git a/src/connstat.cpp b/src/connstat.cpp new file mode 100644 index 000000000..b1dab3b06 --- /dev/null +++ b/src/connstat.cpp @@ -0,0 +1,137 @@ +#include "connstat.h" + +#include +#include +#include +#include +#include + +namespace dht { +namespace net { + +ConnectivityStatus::ConnectivityStatus() + : nlsk(nlsk_init()) + , logger_(new dht::Logger) +{ + nlsk_setup(nlsk.get()); + + thrd_ = std::thread([this] () { nl_event_loop_thrd(nlsk.get()); }); +} + +void +ConnectivityStatus::setEventListener(ConnectionEventCb ucb, Event event) +{ + switch (event) { + case Event::NEWADDR: + case Event::DELADDR: + case Event::ADDR: + nl_socket_add_memberships(nlsk.get(), RTNLGRP_IPV6_IFADDR, RTNLGRP_NONE); + nl_socket_add_memberships(nlsk.get(), RTNLGRP_IPV4_IFADDR, RTNLGRP_NONE); + break; + default: + break; + } + event_cbs[event] = ucb; +} + +void +ConnectivityStatus::removeEventListener(Event event) +{ + event_cbs.erase(event); + switch (event) { + case Event::NEWADDR: + nl_socket_drop_memberships(nlsk.get(), RTNLGRP_IPV6_IFADDR, RTNLGRP_NONE); + break; + case Event::DELADDR: + nl_socket_drop_memberships(nlsk.get(), RTNLGRP_IPV4_IFADDR, RTNLGRP_NONE); + break; + default: + break; + } +} + +void +ConnectivityStatus::executer(Event event) +{ + auto cb = event_cbs.find(event); + if (cb != event_cbs.end() && cb->second) + (cb->second)(event); +} + + +void +ConnectivityStatus::nlsk_setup(nl_sock* nlsk) +{ + nl_socket_disable_seq_check(nlsk); + + nl_socket_modify_cb(nlsk, NL_CB_VALID, NL_CB_CUSTOM, [](nl_msg* msg, void* data) -> int { + return ((ConnectivityStatus*)data)->nl_event_cb(msg); + }, (void *)this); + + nl_connect(nlsk, NETLINK_ROUTE); +} + +ConnectivityStatus::NlPtr +ConnectivityStatus::nlsk_init(void) +{ + NlPtr ret(nl_socket_alloc(), &nl_socket_free); + if (not ret.get()) + throw std::runtime_error("couldn't allocate netlink socket!\n"); + + + return ret; +} + +void +ConnectivityStatus::get_neigh_state(struct nl_msg* msg) +{ + struct nlmsghdr *h = nlmsg_hdr(msg); + struct ndmsg *r = (struct ndmsg*)nlmsg_data(h); + + switch (r->ndm_state) { + case NUD_REACHABLE: + executer(Event::NEWNEIGH); + break; + default: + break; + } +} + +int +ConnectivityStatus::nl_event_cb(struct nl_msg* msg) +{ + struct nlmsghdr *h = nlmsg_hdr(msg); + + int status = 0; + switch (h->nlmsg_type) { + case RTM_NEWADDR: + executer(Event::ADDR); + executer(Event::NEWADDR); + break; + case RTM_DELADDR: + executer(Event::ADDR); + executer(Event::DELADDR); + break; + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + case RTM_NEWNEIGHTBL: + get_neigh_state(msg); + break; + default: + status = 1; + break; + } + return status; + +} + +void +ConnectivityStatus::nl_event_loop_thrd(nl_sock *nlsk) +{ + while (nl_recvmsgs_default(nlsk) >= 0) + ; +} + +} /* namespace net */ +} /* namespace dht */ +