Compare commits

...
283 Commits
Author SHA1 Message Date
Adrien Béraud 1caf49565a wip 2018-04-21 17:16:37 -04:00
Adrien Béraud 1c9acfe001 proxy client: call opFailed on failure 2018-04-21 15:08:48 -04:00
Adrien Béraud c6cef856f0 proxy server: cleanup 2018-04-21 12:23:33 -04:00
Adrien Béraud 9f503dbb65 proxy client: loopSignal on put refresh notification 2018-04-21 12:22:58 -04:00
Adrien Béraud 7e4478123f proxy client: use time_point::max() on persistent put 2018-04-21 12:22:00 -04:00
Adrien Béraud e54d26aaf7 dhtrunner: handle push notification on main dht loop 2018-04-21 12:21:00 -04:00
Adrien Béraud ee749d1ea7 update to version 1.7.1 2018-04-20 22:21:15 -04:00
Adrien Béraud a0d77e7b19 Merge pull request #283 from savoirfairelinux/fixPush
proxy server, fix push listeners and avoid flooding nodes
2018-04-20 18:46:04 -04:00
Sébastien Blin 9421984a89 proxy: cancelPushListeners using clientId instead of token
All listeners uses a token equals to 0, we need to identify listeners
with client_id instead of a token.
2018-04-20 16:37:12 -04:00
Sébastien Blin 37ef1c2cff proxy: set clientId to listener
clientId was not properly set. so every time someone launch a node
and do a listen on a hash, a new listener is generated and it results
by flooding the node of push notifications.
2018-04-20 16:19:03 -04:00
Sébastien Blin 0a04a7f28d proxy: unsubscribe is now valid with token equals 0
Because we now have one listener for each client by hash, the token
is unnecessary here. Moreover, this check is invalid cause we use
a token equal to 0.
2018-04-20 15:25:57 -04:00
Sébastien Blin b47a85fe09 Merge pull request #282 from savoirfairelinux/op_time_cache
op cache: keep listeners alive for some time after cancel
2018-04-20 13:43:32 -04:00
Adrien Béraud 4b7b3cd518 dhtrunner: use proper token with cancelListen(future<token>) 2018-04-20 13:35:13 -04:00
Adrien Béraud 1a2d783c2a op cache: keep listeners alive for some time after cancel 2018-04-20 11:35:45 -04:00
Adrien Béraud 8b6dcb1cd5 dhtnode: allow to trigger connectivity change 2018-04-19 11:31:08 -04:00
Adrien Béraud e140116fed proxy client: close old socket when restarting listeners 2018-04-19 11:31:08 -04:00
Sébastien Blin c16a65a055 Merge pull request #276 from savoirfairelinux/proxy_refresh
Proxy/subscribe: improve refresh/cancel behavior
2018-04-16 15:41:19 -04:00
Adrien Béraud da1b7d4615 proxy server: send refresh notification with margin 2018-04-16 15:05:28 -04:00
Adrien Béraud 7b3ce69b62 proxy client: send remote token on cancel 2018-04-16 15:04:57 -04:00
Adrien Béraud 8a52a7f72a build: add missing files, cleanup 2018-04-16 14:17:52 -04:00
Adrien Béraud abb9f9291b proxy server: avoid unneeded copy 2018-04-14 18:15:02 -04:00
Adrien Béraud e533f5686e proxy server: use std::lock to avoid deadlock 2018-04-14 18:14:42 -04:00
Adrien Béraud 4234908b69 proxy server: check refresh job 2018-04-13 15:59:46 -04:00
Adrien Béraud c10c4251b0 proxy server: print stats to stdout once in a while 2018-04-13 15:52:56 -04:00
Adrien Béraud 8b90c47379 proxy server: add logs 2018-04-13 15:06:49 -04:00
Sébastien Blin fae6b13897 Merge pull request #275 from savoirfairelinux/proxy
proxy server: catch exceptions in requests, always close connection
2018-04-13 14:17:04 -04:00
Adrien Béraud a48a965c1a proxy server: catch exceptions in requests, always close connection 2018-04-13 14:01:09 -04:00
Adrien Béraud ab0f4f6f17 proxy client: refresh now in case of refresh notification 2018-04-13 11:14:40 -04:00
Adrien Béraud e3114cd5b9 proxy client: catch name resolving exception 2018-04-13 11:09:34 -04:00
Adrien Béraud 8489433060 network engine: expire node on next run on network failure 2018-04-11 11:27:46 -04:00
Sébastien Blin 6594d90042 Merge pull request #273 from savoirfairelinux/proxy_single_push
proxy server: keep only one push listener per token/key
2018-04-11 10:24:14 -04:00
Adrien Béraud 7c5145bca5 dhtnode: don't maintain storage 2018-04-10 16:01:14 -04:00
Adrien Béraud e088cd10be proxy server: keep only one push listener per token.key.clientId 2018-04-10 16:01:14 -04:00
Adrien Béraud 6a2f61a0db dht: fix compilation warnings 2018-04-10 12:48:19 -04:00
Adrien Béraud 7b88a2122a proxy: always use string tokens 2018-04-10 12:48:19 -04:00
Sébastien Blin f9cf95d537 Merge pull request #272 from savoirfairelinux/proxy_client
Proxy client: multiple improvements and bug fixes
2018-04-09 12:21:29 -04:00
Adrien Béraud 4884306135 proxy client: restart listeners on reconnect
* track IPv4, IPv6 connectivity seperately
* properly restart listeners when connected
* get proxy info in parallel for multiple AF
2018-04-08 19:54:43 -04:00
Adrien Béraud fd11a4b3e4 proxy client: prevent callback during cancelAllListeners 2018-04-08 19:54:00 -04:00
Adrien Béraud b3c637536f proxy client: prevent use-after-free after destruction 2018-04-08 19:54:00 -04:00
Adrien Béraud 1436af0321 value cache: prevent callback copy on move 2018-04-08 19:54:00 -04:00
Adrien Béraud 1debe012c1 proxy client: restart listeners on connectivity change 2018-04-08 19:54:00 -04:00
Adrien Béraud 368a842d2e proxy client: don't block resolving address 2018-04-08 19:53:59 -04:00
Adrien Béraud d250e9b630 proxy client: avoid sending listen with push notifications 2018-04-08 19:53:59 -04:00
Adrien Béraud 3ff6863287 proxy client: add logs 2018-04-08 19:53:59 -04:00
Sébastien Blin 96621ea332 Merge pull request #271 from savoirfairelinux/proxy_client_use_after_free
proxy client: retrieve search from callback
2018-04-06 13:13:07 -04:00
Adrien Béraud 567fa1c703 proxy client: retrieve search from callback 2018-04-06 11:43:51 -04:00
Adrien Béraud 20ab71aba6 securedht: check public key 2018-04-05 14:23:15 -04:00
Adrien Béraud 1acace1b91 proxy client: add missing mutex, cleanup 2018-04-05 02:35:01 -04:00
Adrien Béraud 2f423fec48 crypto: check public key for null before pack or print 2018-04-05 02:33:58 -04:00
Adrien Béraud 7be550bf94 value cache: use single callback 2018-04-05 02:08:58 -04:00
Adrien Béraud a8c0340bf1 Merge pull request #269 from AmarOk1412/removeCallbackId
proxy: remove useless callback_id
2018-04-03 12:26:52 -04:00
Sébastien Blin 5b71af3cec proxy: remove useless callback_id
callback_id was used for token problems which are not present anymore.
Now token is sufficient and we can remove callback_id.
2018-04-03 10:43:56 -04:00
Adrien Béraud fcf7111872 scheduler: avoid possible overflow with buggy stdlib 2018-03-29 11:58:26 -04:00
Adrien Béraud 38ca7b851b Merge pull request #267 from AmarOk1412/cpuusage
scheduler: avoid overflow causing 100% cpu usage
2018-03-29 11:42:13 -04:00
Sébastien Blin 62bbce76f2 scheduler: avoid overflow causing 100% cpu usage 2018-03-29 11:40:04 -04:00
Adrien Béraud d9f6731de2 Merge pull request #265 from AmarOk1412/fixSplit
proxy_client: fix address split
2018-03-29 11:10:32 -04:00
Sébastien Blin 393a403ff2 proxy_client: fix address split 2018-03-29 11:00:33 -04:00
Adrien Béraud 62e4655d4f Merge pull request #264 from AmarOk1412/fixPublicAddr
dht_proxy_client: fix getPublicAdresss parsing
2018-03-28 16:44:34 -04:00
Sébastien Blin 60d43319e2 dht_proxy_client: fix getPublicAdresss
Retrieve all public ips (Ipv6 + v4). Has to resolve all proxy
address (so two get requests to get all ips)
2018-03-28 15:59:04 -04:00
Sébastien Blin 7d3c613347 Merge pull request #263 from savoirfairelinux/value_cache
Cache: improve value expiration handling
2018-03-28 10:11:12 -04:00
Adrien Béraud d2df93c75e network engine: send nodes in listen confirmation 2018-03-28 09:54:04 -04:00
Adrien Béraud 0edee443f5 cache: improve value expiration handling 2018-03-28 09:54:04 -04:00
Sébastien Blin 8c85f84b8a Merge pull request #261 from savoirfairelinux/dhtrunner_unit_test
tests: add initial unit test for DhtRunner
2018-03-21 11:38:05 -04:00
Adrien Béraud cea0d4f62e tests: add initial unit test for DhtRunner 2018-03-21 11:22:21 -04:00
Adrien Béraud 203150851f OpCache: keep cache alive during callback processing 2018-03-19 20:11:12 -04:00
Adrien Béraud 9a5ef25179 update to version 1.7.0rc1 2018-03-17 23:12:38 -04:00
Adrien Béraud a5137e8ac4 tests: fix build with autotools 2018-03-17 14:47:23 -04:00
Adrien Béraud c129a24492 travis: use 5 build configurations
Merge all push configurations to reduce build time
as Travis provides 5 free simultaneous builds.
The 'all' configuration is renamed 'push' to reflect the change
2018-03-17 14:40:50 -04:00
Adrien Béraud 8237f32bd5 travis: update Dockerfiles, change base OS to Ubuntu 16.04 2018-03-17 14:32:09 -04:00
Sébastien Blin 546e63edaf Merge pull request #258 from savoirfairelinux/value_cache
Add a value cache for each nodes. This cache avoids network packets by grouping LISTEN operations on the same hash.
2018-03-16 13:55:24 -04:00
Adrien Béraud 647de16f1c proxy client: implement getLocal(), getLocalById() 2018-03-16 13:34:41 -04:00
Adrien Béraud 2f4acd26d6 dhtinterface: add listen() callback to signal expired values 2018-03-16 13:34:41 -04:00
Adrien Béraud 48bb804792 proxy client: add cache for values and operations 2018-03-16 13:34:40 -04:00
Adrien Béraud 4261bd2c85 dhtinterface: remove resubscribe 2018-03-16 13:34:32 -04:00
Adrien Béraud 12db1f88dc dht: introduce cache for values and operations
Multiplex multiple listen operations on a single key through
a unique listen operation on the network.
2018-03-16 13:34:32 -04:00
Adrien Béraud 1617c79e5d network: add signaling for expired, refreshed values 2018-03-14 23:39:35 -04:00
Adrien Béraud 80b944d943 dhtnode: prevent crash in cancelListen 2018-03-14 23:39:35 -04:00
Adrien Béraud 74bfced9b9 Merge pull request #255 from AmarOk1412/tests
tests: add unit tests structure to opendht
2018-03-14 23:00:30 -04:00
Sébastien Blin 1c10872ea2 tests: add unit tests structure to opendht
+ Unit testing requires cppunit.
+ add options for CMake and Autotools to build opendht_unit_tests.
+ add sample tests for InfoHash
+ modify Dockerfiles and Travis build to run `make test`
+ Change lowbit signature to return an int instead of an unsigned.
2018-03-14 15:26:47 -04:00
Adrien Béraud 2b2e8c2a72 Merge pull request #260 from AmarOk1412/fixConnectivityStatus
proxy_client: avoid to call getConnectivityStatus() when destroying
2018-03-14 15:22:20 -04:00
Sébastien Blin 2315dc089a proxy_client: avoid to call getConnectivityStatus() when destroying
Because getConnectivityStatus is called each time an operation fails,
when destroying the proxy_client, we stop all operations so it fails.
2018-03-14 11:33:01 -04:00
Adrien Béraud d7c0e9af38 update to version 1.6.1 2018-03-06 17:30:07 -05:00
Adrien Béraud 460cf858d3 argon2: bump submodule to latest release 2018-03-06 17:24:09 -05:00
Adrien Béraud c81da2dd7d indexation: enable by default 2018-03-06 10:29:01 -05:00
Adrien Béraud b02f8e98c4 filter: add Value vector filter 2018-03-06 10:29:01 -05:00
Adrien Béraud ca9a367803 value: add TypeStore 2018-03-06 10:29:01 -05:00
Adrien Béraud 09f080220f Merge pull request #252 from AmarOk1412/fixDuplication
dht_proxy_client: fix getCallback for push notifications
2018-03-06 10:24:29 -05:00
Sébastien Blin 547fa2eff5 dht_proxy_client: fix getCallback for push notifications
Note: Also fix Value deserialization
2018-03-05 19:15:04 -05:00
Adrien Béraud 2a8f21d4aa Merge pull request #251 from AmarOk1412/fixcancelListen
proxyclient: avoid useless opFailed when correctly cancel a listen
2018-03-01 16:28:18 -05:00
Sébastien Blin 6f4283b7d5 proxyclient: avoid useless opFailed when correctly cancel a listen 2018-03-01 16:11:49 -05:00
Adrien Béraud babceb902a readme: fix typo 2018-02-28 21:53:52 -05:00
Adrien Béraud 26bd4e7141 readme: update 2018-02-28 21:37:29 -05:00
Sébastien Blin 1531e13ca4 build: make indexation optional 2018-02-28 08:17:09 -06:00
Adrien Béraud c9cbced0c8 build: make indexation optional 2018-02-27 21:48:49 -05:00
Adrien Béraud 2cf2112aec dhtnode: simplify macros 2018-02-27 21:33:48 -05:00
Adrien Béraud 012f6ed2f6 Merge pull request #247 from AmarOk1412/fixCancelListen
proxyserver: fix cancelListen and reduce push notifications number
2018-02-26 17:19:32 -05:00
Sébastien Blin fcac10c4c8 proxyserver: fix cancelListen and reduce push notifications number 2018-02-26 17:01:23 -05:00
Adrien Béraud f2b692c716 Merge pull request #245 from AmarOk1412/fixCancelListen
dhtproxy: fix cancelListen with pushNotifications
2018-02-26 15:37:00 -05:00
Sébastien Blin 22ef53acef dhtproxy: fix cancelListen with pushNotifications
Fill the body of the UNSUBSCRIBE method and fix dhtnode application
2018-02-26 15:25:48 -05:00
Adrien Béraud 2af70e6d50 Merge pull request #243 from AmarOk1412/fixPushBuild
pushnotifications: fix build without push notifications, improve travis script
2018-02-26 14:21:34 -05:00
Sébastien Blin 63c9c607bd pushnotifications: fix build without push notifications, improve travis script 2018-02-26 13:27:06 -05:00
Adrien Béraud cab0813b17 dht: reduce log verbosity 2018-02-24 15:15:49 -05:00
Adrien Béraud 06cc490ee4 listener: remove unused field 2018-02-24 15:05:14 -05:00
Adrien Béraud 6821988ddf node: use thread local random device 2018-02-24 14:30:40 -05:00
Adrien Béraud 8cce9fcb85 search: remove getNextStepTime 2018-02-23 13:50:25 -05:00
Adrien Béraud 56caae019c proxy server: add push notification ttl 2018-02-19 02:10:56 +01:00
Adrien Béraud f5afcbf62b proxy server: use high priority notifications
Normal priority notifications may be delayed.
2018-02-16 01:54:44 +01:00
Adrien Béraud 0957d30e86 y2038 bug: don't allow time wrap for certificate validity 2018-02-10 23:21:31 +01:00
Adrien Béraud 3a632b1544 proxy server: add logs 2018-02-10 14:31:23 +01:00
Adrien Béraud 2c0f4a897c dhtnode: fix cancelListen 2018-02-10 14:31:00 +01:00
Adrien Béraud be6a5671ed y2038 bug: change certificate duration to 10 years
A certificate duration of 20 years was making certificate expire
after 2038 which is not supported on many 32 bits platforms.
Reduce to 10 years, which gives another decade to deprecate 32-bits platforms.
2018-02-09 01:40:03 +01:00
Adrien Béraud 61ac297b1b crypto: add method to read Certificate activation date 2018-02-09 00:42:25 +01:00
Adrien Béraud da867dd07c Merge pull request #239 from kiddouk/fix/dhtnode-identity
Generates RSA pairs for dhtnode instead of EC cert
2018-02-08 13:05:46 +01:00
Sébastien Requiem 962249ec75 Generates RSA pairs for dhtnode instead of EC cert
* dhtnode was generating twice an EC cert which rendered encryption
impossible since opendht lib is expecting an RSA public key to encrypt
the data for the destination.

* fix savoirfairelinux/opendht#235
2018-02-08 10:56:17 +01:00
Adrien Béraud c03ba57895 dhtnode: use explicit unique_ptr constructor 2018-02-07 17:40:59 +01:00
Adrien Béraud e2224fac5a Merge pull request #209 from dctremblay/readme-update-gcc-version
README : update GCC required version from 4.8 to 5.2
2018-02-01 00:52:39 +01:00
Adrien Béraud 48aaaf1e5d Merge branch 'master' into readme-update-gcc-version 2018-02-01 00:51:41 +01:00
Adrien Béraud 80ee256712 update to version 1.6.0rc1
* added REST proxy client implementing the DHT API
* added optional support for push notifications through the proxy server,
  using the gorush REST API: https://github.com/appleboy/gorush#web-api
* DhtRunner: now able to use DhtProxyClient as a DHT node
* DhtRunner: can switch between local or proxy node during runtime
* SockAddr: added methods to handle IPv6-mapped IPv4 addresses
* iOS: handle SIGPIPE on UDP sockets by recreating sockets
* fixed occasional crash during packet processing
2018-01-31 18:57:54 +01:00
Adrien Béraud e274ffd83b headers: unify API across build options 2018-01-31 18:07:43 +01:00
Adrien Béraud bd9496e2ba crypto: fix build on GnuTLS 3.3 2018-01-26 15:10:07 -05:00
Adrien Béraud 4d4ba2be96 Merge pull request #234 from atraczyk/master
fix bad paths
2018-01-24 17:31:36 -05:00
atraczyk 680e30be9b fix bad paths 2018-01-24 17:30:21 -05:00
Adrien Béraud 233301c3f3 dhtrunner: use proxy by default if configured 2018-01-23 12:19:38 -05:00
Adrien Béraud 0db21772c1 Merge pull request #232 from atraczyk/sigpipe
dhtrunner: pass SockAddrs by value to startNetwork
2018-01-23 11:34:14 -05:00
Adrien Béraud e6a2d96622 net: restart socket in case of sigpipe 2018-01-23 11:34:14 -05:00
atraczyk e5d83d47be dhtrunner: don't pass SockAddrs by reference to startNetwork 2018-01-23 11:34:14 -05:00
Adrien Béraud 3d77c76a4d dhtrunner: restart listeners only if one exists 2018-01-23 11:31:12 -05:00
Adrien Béraud 043d5a7c7c dhtrunner: check dht instance in setLocalCertificateStore 2018-01-19 14:05:21 -05:00
Adrien Béraud 636eb1972d proxy client: improve robustness, switch behavior 2018-01-18 16:36:13 -05:00
Adrien Béraud 27a250d2c0 proxyclient: fall to disconnected in case of connectivity failure 2018-01-17 12:09:39 -05:00
Adrien Béraud 02f4c81f2a dhtrunner: improve proxy listen handling 2018-01-17 11:40:18 -05:00
Adrien Béraud 0420ebcce3 dhtinterface: pass listen filters by value 2018-01-17 11:40:18 -05:00
Adrien Béraud 15e3696b88 proxy client: fix build when push notifications are disabled 2018-01-13 16:16:19 -05:00
Adrien Béraud ca693a6ac1 pushNotificationReceived: take a map<string, string>
JSON is used for the DHT proxy protocol and to communicate with gorush.
Push notifications services might use any format, not necessarily JSON.
Push values are received as a map<string, string>, so receive them in
that format.
2018-01-12 13:20:07 -05:00
Adrien Béraud 1d6a307b6b proxy server: send data push message instead of notification 2018-01-11 17:01:10 -05:00
Adrien Béraud 227cca883c dhtrunner: start proxy ASAP, use local dht for node stats/import/export 2018-01-11 14:24:59 -05:00
Adrien Béraud ef82a7ecba proxy client: make proxy status check asynchronous 2018-01-11 14:24:59 -05:00
Adrien Béraud 6e9f2cda79 SockAddr: add isMappedIPv4, getMappedIPv4 2018-01-11 13:29:18 -05:00
Adrien Béraud 10b58c5ce7 SockAddr: add constructor from simple sockaddr* 2018-01-11 13:29:18 -05:00
Adrien Béraud ab731cd7d0 Merge pull request #228 from AmarOk1412/push_notifications_client
Add support for push notifications
2018-01-11 13:14:43 -05:00
Adrien Béraud a0d8f4734a dhtproxy: allow token refresh, set clientID (#6)
* add method to set the push token independently from other configuration,
  since the push token can be updated during runtime.
* allow to provide a "clientID" to be sent in push notifications,
  used to demultiplex push notifications to multiple OpenDHT nodes running
  on the same host. For instance Ring uses the accountID as the clientID
  to know which account received the push notification.
* use {"platform":"android"/"ios"} to communicate the platform instead of {"isAndroid":true/false}.
2018-01-10 16:32:57 -05:00
Adrien Béraud 237859d8c3 Merge branch 'master' into push_notifications_client 2017-12-20 17:51:01 +01:00
Adrien Béraud 24dec0682f Merge pull request #231 from AmarOk1412/fixLog
dht_interface: fix logger issues
2017-12-19 02:11:25 +01:00
AmarOk 4b46f6a821 dht_interface: fix logger issues
Make DHT_LOG static to use it in the whole library
2017-12-15 12:45:10 -06:00
Sébastien Blin 37a9a3d9da dhtproxy: inject push notifications in opendht
+ add the ability to inject push notifications in opendht to call
correct callbacks
+ if a timeout occurs and no cancelListen was called, relaunch the
listen operation
+ Pass devicekey to enable push notifications for DhtProxyClient
+ Update sockaddr to fix the build on all platforms

Note: update dhtnode
2017-12-12 15:03:02 -06:00
Sébastien Blin 75cca66d7e dhtproxy: support push notifications for mobile clients
+ Add SUBSCRIBE and UNSUBSCRIBE for DhtProxyServer
+ Add the ability to stop Listen via a timeout (and send a push
Notification for the client)
+ Add the ability to use gorush to send push notifications

Note: Update dhtnode
2017-12-12 10:24:54 -06:00
Adrien Béraud 762a1c7ad2 Merge pull request #220 from AmarOk1412/dhtproxyclient_merge
proxy: implement DhtProxyClient
2017-12-12 13:38:23 +01:00
AmarOk d19ba2cb41 proxyserver: add lock_guard to avoid to manually unlock 2017-12-06 14:43:28 -06:00
AmarOk 557323deff refactor: use new jsoncpp API
Use the latest jsoncpp API to remove depreciation warning.
2017-12-06 08:49:24 -06:00
AmarOk 29ffbf6aa7 dhtrunner: remove DhtRunner::getAddrInfo
Was replaced by SockAddr::resolve
2017-12-05 10:02:38 -06:00
Adrien Béraud aaa577d9e3 proxy_client: use atomic bool for shared state flags (#3) 2017-12-05 09:50:38 -06:00
Sébastien Blin 333c44dbd3 proxy_client: don't block for callback 2017-12-05 09:05:35 -06:00
Adrien Béraud 8243bc4c3f proxy_client: don't block for callback
Use shared flags to avoid blocking for the callback
while respecting the callback return value.
2017-12-05 15:51:39 +01:00
AmarOk b1a9742889 dhtproxyclient: add lock_guards for mutex and clean code 2017-12-04 09:42:37 -06:00
Adrien Béraud d2f91af946 node: use shared_ptr to manage Sockets 2017-12-02 16:03:27 +01:00
Sébastien Blin 5adc5b4c94 README: update Restbed dependency informations 2017-12-01 14:28:32 -05:00
Adrien Béraud f9d48cd15a Merge pull request #227 from atraczyk/master
Build: add new files to VS project and adjust filters
2017-11-30 00:17:29 +01:00
Sébastien Blin e7d8bc4275 proxyclient: erase finished operation threads 2017-11-29 14:44:02 -05:00
atraczyk 507326cebc Build: add new files to VS project and adjust filters 2017-11-29 13:46:07 -05:00
Adrien Béraud b24832e0e1 Merge pull request #226 from atraczyk/master
InfoHash: fix stream operator<< declaration
2017-11-29 17:14:03 +01:00
atraczyk 6cdcd183b4 InfoHash: fix stream operator<< declaration 2017-11-29 11:08:39 -05:00
Sébastien Blin eb750f5b7f proxy_client: execute callbacks in main thread 2017-11-27 17:51:33 -05:00
Sébastien Blin 5967c89c23 proxy: don't reinit dhtproxy multiple times and forward encrypted messages
+ Having a null dht_proxy_client can lead to using nullptr.
+ securedht should forward messages if we are using a proxy.
+ Protect callback segfault
2017-11-22 16:09:45 -05:00
Sébastien Blin 4a83b36dcf tools: add the ability to use a proxy for dhtchat 2017-11-21 13:37:18 -05:00
Sébastien Blin eb1f9d64e1 dhtproxyclient: add listen ability.
+ Fix LISTEN endpoint for DhtProxyServer
= DhtProxyServer use multiple worker threads
+ Add listen/cancelListen for DhtProxyClient
+ Add connectivityChanged.
2017-11-20 16:01:44 -05:00
Sébastien Blin cb72c104fd proxy: implement basic DhtProxyClient with the ability to get and put.
+ Add an abstract class to describe the Dht
+ Now, we can modify the Dht linked to a SecureDht. So we can choose
between the classic Dht and the Dht using a proxy.
+ Implement a basic client which can Get and Put values on the Dht using
the proxy.

Note: Update dhtnode to have the ability to use the DhtProxyClient.
Todo: Implement Listen, ping, connectivityChanged for the client.
2017-11-20 15:45:29 -05:00
Adrien Béraud 5fde50be4a dhtrunner: use const char* for setopt value
fix build on Win32
2017-11-17 19:34:31 +01:00
Adrien Béraud 1c16e6f38d update to version 1.5.0
* added REST server implementing OpenDHT proxy API
* tools: added simple Web client using the proxy server
* tools: allow dhtnode to run a proxy server
* tools: allow dhtnode to use any string as key
* InfoHash: added operator bool()
* minor performance improvement
* SockAddr: fix endianness issue
* net: fix incorrect reported address parsing
* minor memory leak fixes
2017-11-17 18:51:45 +01:00
Adrien Béraud 6790d1ec5c SockAddr: set service to null if string is empty 2017-11-17 18:31:28 +01:00
Adrien Béraud 4dc1847978 autotools: fix build without proxy server 2017-11-17 16:42:04 +01:00
Adrien Béraud 3a61692618 net: set MSG_NOSIGNAL if possible 2017-11-17 02:20:31 +01:00
Adrien Béraud 6f9afac0a5 dhtrunner: cleanup 2017-11-17 02:12:03 +01:00
Adrien Béraud b17c7c2965 dht: reserve address vector size 2017-11-17 02:06:32 +01:00
Adrien Béraud 6d238836ad dht: return empty search on error 2017-11-17 01:01:47 +01:00
Adrien Béraud 5115fab9e6 dht: increase max searches limit 2017-11-16 16:49:59 +01:00
Adrien Béraud 93498ecf0b tools: JS node: cleanup 2017-11-14 21:55:03 +01:00
Adrien Béraud 72d790953a tools: JS node: allow to set the proxy address 2017-11-14 20:58:18 +01:00
Adrien Béraud 72b556a09b tools: JS node: cleanup 2017-11-14 19:38:55 +01:00
Adrien Béraud 747f86f8f4 dhtrunner: use SockAddr::resolve 2017-11-10 17:59:05 -05:00
Adrien Béraud 161769f4ec SockAddr: use host endianess for bitwise-shift operations 2017-11-10 17:48:36 -05:00
Adrien Béraud 9948079c97 SockAddr: add resolve() 2017-11-10 17:48:36 -05:00
Adrien Béraud 607e5eeccf tools: use https url 2017-11-10 17:29:24 -05:00
Adrien Béraud 111ac57b6f tools: JS node: add listen support 2017-11-09 12:23:30 -05:00
Adrien Béraud 153641dde5 tools: add JavaScript proxy node 2017-11-08 15:20:06 -05:00
Adrien Béraud 113bebabd4 DhtRunner: remove log 2017-11-08 15:14:01 -05:00
Adrien Béraud b549055034 DhtRunner: use SockAddr 2017-11-08 15:12:43 -05:00
Adrien Béraud 7d7410b472 dhtnode: allow to use arbitrary string as key
Key will be h(key).
2017-11-08 15:12:43 -05:00
Adrien Béraud d7aaa4ae56 proxy server: add Access-Control-Allow-Origin 2017-11-08 15:12:43 -05:00
Adrien Béraud 5a049f89f3 cmake: find Jsoncpp using pkgconfig 2017-11-08 15:12:43 -05:00
Sébastien Blin 65e88f2fa6 build: toInfoHash->to 2017-11-08 15:07:04 -05:00
Adrien Béraud 4430b1e616 Merge pull request #217 from AmarOk1412/dhtproxy
Communicate with OpenDHT through a REST API
2017-11-08 14:35:31 -05:00
Sébastien Blin e2c534df5a add std::bind, fix put, avoid overflow on base64 2017-11-08 14:33:07 -05:00
Sébastien Blin 2ca2835c9b add options method handler 2017-11-08 12:14:44 -05:00
Sébastien Blin bbe922dd7d remove headers, fix HTTP status, fix get / 2017-11-07 16:08:55 -05:00
Sébastien Blin 38ad5b9ef2 add --proxyserver option for dhtnode 2017-11-06 16:49:46 -05:00
Sébastien Blin 67ba435ba6 base64: take the old implementation from ring-daemon 2017-11-06 16:24:00 -05:00
Sébastien Blin 6413571301 clean code 2017-11-06 12:53:35 -05:00
Adrien Beraud 0dccae68e6 InfoHash: make false in case of parsing error 2017-11-03 16:03:11 -04:00
Adrien Beraud 92320cef59 InfoHash: throw in case of invalid input string 2017-11-03 14:03:34 -04:00
Adrien Beraud bf4e838e20 InfoHash: return stream in operator>> 2017-11-03 14:00:49 -04:00
Adrien Beraud 88b4233bda python: update InfoHash bool operator 2017-11-03 14:00:49 -04:00
Adrien Beraud e9eb97ea9a InfoHash: improve hex parsing performance 2017-11-02 17:42:45 -04:00
Adrien Beraud 0e865976f9 InfoHash: unify with Hash<N> 2017-11-02 14:28:09 -04:00
Adrien Beraud 87dc080bbf crypto: add raw hash function 2017-11-02 14:25:46 -04:00
Sébastien Blin 9abc88f378 add --enable-proxy-server option for autotools 2017-11-01 17:11:14 -04:00
Adrien Beraud 834d62e6e9 InfoHash: use bool operator where relevent 2017-11-01 16:28:17 -04:00
Sébastien Blin 61a148e83f update README.md 2017-11-01 14:52:29 -04:00
Sébastien Blin 8e43775394 update dhtnode + clean code 2017-11-01 14:52:29 -04:00
Sébastien Blin 8344138bc8 add optional + clean start/stop 2017-11-01 14:52:29 -04:00
Sébastien Blin dc66b3847a make code for DHT Proxy Server optionnal 2017-11-01 14:52:29 -04:00
Sébastien Blin d608da4307 cmake: add optionnal dependencies for proxy 2017-11-01 14:52:29 -04:00
Sébastien Blin 7ddd6629e3 add getFiltered 2017-11-01 14:52:29 -04:00
Sébastien Blin 20abc3e495 add LISTEN endpoint 2017-11-01 14:52:29 -04:00
Sébastien Blin c4ba8670e4 add Putxxx value endpoints 2017-11-01 14:52:29 -04:00
Sébastien Blin a3348c85d6 value to JSON and begin to clean endpoints 2017-11-01 14:52:29 -04:00
Sébastien Blin ca465d7159 set timeout to a high value + add jsoncpp 2017-11-01 14:52:29 -04:00
Sébastien Blin 1413d32ee6 add listen endpoint 2017-11-01 14:52:29 -04:00
Sébastien Blin 58fdfa9452 add get hash endpoint 2017-11-01 14:51:36 -04:00
Sébastien Blin 6f641073ae proxy put and putEncrypted 2017-11-01 14:51:36 -04:00
Sébastien Blin 32153b11ab proxy putSigned 2017-11-01 14:51:36 -04:00
Sébastien Blin e33ac02351 test restbed 2017-11-01 14:51:36 -04:00
Adrien Beraud dc99919338 InfoHash: add explicit operator bool() 2017-11-01 11:59:11 -04:00
Adrien Béraud fcaaa01b2d network engine: add "value update" message type 2017-10-30 13:12:16 -04:00
Adrien Béraud 8cd3ff9fc4 network engine: remove unused constant 2017-10-30 13:12:16 -04:00
Adrien Béraud 0a130c7045 dht: update permanent state on announce 2017-10-30 13:12:16 -04:00
Adrien Béraud eb9f314356 dhtnode: allow to perform and cancel persistent put 2017-10-30 13:12:16 -04:00
Adrien Béraud a401aea132 dht: simplify searchSendAnnounceValue 2017-10-30 13:12:16 -04:00
Adrien Béraud 41ebbe21d4 node cache: expire nodes on destructions
Expiring nodes on cache destruction allow to cancel
any remaining request before the dht instance is destructed,
since Node instances might remain alive after the dht instance
is destroyed.
2017-10-30 13:12:16 -04:00
Adrien Béraud 6378840d33 dhtnode: allow to cancel a listen operation 2017-10-30 13:12:16 -04:00
Adrien Béraud 64f0aa1532 parsed message: don't override the reported address family 2017-10-30 13:12:16 -04:00
Adrien Béraud 63366722a9 dht: reduce verbosity of storage log 2017-10-21 18:01:32 +02:00
Adrien Béraud cd17858ae1 dht: periodic cleanup of unused quota entries 2017-10-21 18:00:46 +02:00
Adrien Béraud f4ebbc6651 update to version 1.4.1
* updated MSVC project
* bug fixes
2017-10-21 01:30:12 +02:00
Adrien Béraud 464339822b Merge pull request #215 from AmarOk1412/typoPeriod
rate_limiter: typo fix, 'Perdiod'->'Period'
2017-10-20 23:56:02 +02:00
Sébastien Blin fe09146609 rate_limiter: typo fix, 'Perdiod'->'Period' 2017-10-20 14:51:35 -04:00
Adrien Béraud a789ffe7ae dht: prevent use-after-free of socket callback 2017-10-20 17:46:27 +02:00
Adrien Béraud 476fbb1160 network: use full 32-bits of transaction IDs
We used to use the first 16 bits for the message type.
2017-10-20 17:42:28 +02:00
Adrien Béraud 4501217c21 Merge pull request #214 from atraczyk/master
remove vs2015 based constexpr error, update vs project files
2017-10-19 01:19:43 +02:00
atraczyk 207675a181 remove vs2015 based constexpr error, update vs project files 2017-10-18 17:50:19 -04:00
Adrien Béraud cf6447dc80 NetworkEngine: use proper type for constant 2017-10-17 19:22:15 +02:00
Adrien Béraud 775eb2e599 update to version 1.4.0
* improved Doxygen documentation
* cmake: add support to build documentation
* cmake: improve build on macOS
* API change: storage policies, getPublicAddress now use SockAddr
* NodeExport: added msgpack serialization
* fixed issue that could cause occasional request failures
* minor bug fixes
* minor performance improvements
2017-10-17 18:37:08 +02:00
Adrien Béraud 376486e64f node: use full range of socket IDs 2017-10-17 15:25:09 +02:00
Adrien Béraud 63013bf374 SearchNode: prevent copy, check request access 2017-10-17 03:17:52 +02:00
Adrien Béraud 488aa771a9 Node: don't expire existing requests 2017-10-16 17:12:33 +02:00
Adrien Béraud 6bc2f19a36 dht: log total number of nodes in onGetValuesDone 2017-10-16 17:12:33 +02:00
Adrien Beraud 2dd94fbfe8 cmake: use CMAKE_THREAD_LIBS_INIT as library 2017-10-16 01:26:40 +02:00
Adrien Béraud be2b9fd7dc NodeCache: handle end() lower_bound for non-empty map 2017-10-15 15:57:14 +02:00
Adrien Béraud 9d4e6e0984 rate limiter: use deterministic maintenance
Using deterministic maintenance sets an upper bound
to the size of the address map.
2017-10-15 01:18:32 +02:00
Adrien Beraud 4856a6b424 PublicKey: getId compatible with HASH_LEN = 32 2017-10-13 15:43:56 +02:00
Adrien Béraud 7df375fc75 NodeCache: fix invalid pointer access 2017-10-13 15:40:09 +02:00
Adrien Béraud 63e0c1266a dht: use OpStatus in put
Factorize shared pointers.
2017-10-13 02:07:14 +02:00
Adrien Béraud 1a54aff7bc storage policies: use SockAddr
API-breaking change to use const SockAddr& instead of sockaddr*.
2017-10-13 01:10:09 +02:00
Adrien Béraud f2b9f8eeda dht: move bucket time update to routing table 2017-10-13 01:10:09 +02:00
Adrien Béraud e9f007025f node: make most fields private 2017-10-13 01:10:09 +02:00
Adrien Béraud 6ce1531e1f node: make id const 2017-10-12 15:44:34 +02:00
Adrien Béraud 84a45c4f91 dht: factorize shared pointer dereference 2017-10-12 15:44:14 +02:00
Adrien Béraud e20ae56cab dht: support client mode
In client mode, a node won't store data
and won't participate in routing.
2017-10-12 15:44:14 +02:00
Adrien Béraud 4ce0168916 network engine: move ParsedMessage to separate file 2017-10-11 23:51:44 +02:00
Adrien Béraud 024dd42e5a dht: remove processMessage 2017-10-11 17:36:20 +02:00
Adrien Béraud 98ec789526 nertwork engine: move tid management to Node 2017-10-11 17:36:20 +02:00
Adrien Béraud 3bb5e7ee12 network engine: move transaction ownership to Node 2017-10-11 17:36:20 +02:00
Adrien Béraud 8c529b7d7f request: add getTid(), getSocket() 2017-10-11 13:47:53 +02:00
Adrien Béraud be7494961c network engine: move socket management to Node 2017-10-11 02:23:56 +02:00
Adrien Béraud bdb0bef0ee network engine: move RequestAnswer out of class 2017-10-11 02:22:03 +02:00
Adrien Béraud 73f26e120d doc: improve documentation 2017-10-10 18:16:33 +02:00
Adrien Béraud 9f4b16e4f5 cmake: add doxygen documentation support
Automatically build documentation if doxygen is found.
2017-10-10 17:45:34 +02:00
Adrien Béraud 323ad774e5 value: restore stream state after change 2017-10-09 15:14:20 +02:00
Adrien Béraud d0bcd39341 IpServiceAnnouncement: use SockAddr 2017-10-09 15:06:34 +02:00
Adrien Béraud 42125367d7 SockAddr: add documentation 2017-10-09 14:46:35 +02:00
Adrien Béraud d87a4ef28c InfoHash: add operator== 2017-10-07 11:35:56 +02:00
Adrien Béraud a3f4388556 InfoHash: increase toFloat precision 2017-10-07 11:35:56 +02:00
Adrien Béraud 97ee4b6fea dht: move onNewNode to RoutingTable 2017-10-07 11:35:56 +02:00
Adrien Béraud 0abd288958 SockAddr: factorize setters 2017-10-05 14:09:56 +02:00
Adrien Béraud 131073e211 SockAddr: cleanup 2017-09-27 11:36:42 +02:00
Adrien Béraud 078c4f064a cmake: use Threads package 2017-09-26 16:42:33 +02:00
Adrien Béraud f17a4e66f5 cmake: add flag to enable address sanitizer and stack protector 2017-09-26 16:35:38 +02:00
Adrien Béraud dd13ebb6d1 cmake: use private linking for private dependencies
Users of the OpenDHT shared library don't need to link
against GnuTLS or Nettle.
2017-09-26 16:34:18 +02:00
Adrien Béraud 1c45f34262 SockAddr: use pointer to sockaddr
Save memory and reduce copies by using a pointer
to a sockaddr structure of the right size instead of
using a full sockaddr_storage structure for every address.
2017-09-26 16:30:29 +02:00
Adrien Beraud a82f575232 NodeExport: add ostream operator 2017-08-30 10:48:49 +02:00
Adrien Beraud 1d84b34cf6 value: avoid checking failbit directly
failbit is implementation defined, use the bool operator instead.
2017-08-30 10:46:30 +02:00
Adrien Beraud bedb51bf52 crypto: add EC curve identity generation 2017-08-28 12:03:15 -04:00
Adrien Béraud 1b9e9452c4 NodeExport: add msgpack serialization 2017-08-28 12:02:09 -04:00
David Côté-Tremblay f69d6426bd README : GCC required version from 4.8 to 5.4 2017-08-10 11:52:46 -04:00
82 changed files with 8034 additions and 2267 deletions

No files matched your search

+9
View File
@@ -48,6 +48,12 @@ Makefile.in
# Python
*.pyc
*.stamp
python/opendht.cpp
python/setup.py
# Doxygen
doc/Doxyfile
# git backup files
*.orig
@@ -55,3 +61,6 @@ Makefile.in
# vim swap files
*.swp
*.swo
# build dir
build
+53 -4
View File
@@ -5,13 +5,62 @@ services:
language: cpp
env:
matrix:
- OPENDHT_TEST_JOB="opendht.classic"
- OPENDHT_TEST_JOB="opendht.llvm"
- OPENDHT_TEST_JOB="opendht.proxyserver"
- OPENDHT_TEST_JOB="opendht.proxyclient"
- OPENDHT_TEST_JOB="opendht.push"
before_install:
- docker pull aberaud/opendht-deps
- docker pull aberaud/opendht-deps-llvm
- |
# non llvm builds
if [[ "$OPENDHT_TEST_JOB" != *"opendht.llvm"* ]]; then
docker pull aberaud/opendht-deps;
fi
- |
# classic build
if [[ "$OPENDHT_TEST_JOB" == *"opendht.llvm"* ]]; then
docker pull aberaud/opendht-deps-llvm
fi
script:
- docker build -f docker/DockerfileTravis .
- docker build -f docker/DockerfileTravisLlvm .
- |
# classic build
if [[ "$OPENDHT_TEST_JOB" == *"opendht.classic"* ]]; then
docker build -t opendht -f docker/DockerfileTravis .;
fi
- |
# proxy builds
if [[ "$OPENDHT_TEST_JOB" != *"opendht.llvm"* ]] && [[ "$OPENDHT_TEST_JOB" != *"opendht.classic"* ]]; then
docker build -t opendht-proxy -f docker/DockerfileTravisProxy .;
options='';
if [[ "$OPENDHT_TEST_JOB" == *"opendht.proxyserver"* ]] || [[ "$OPENDHT_TEST_JOB" == *"opendht.push"* ]]; then
options+='-DOPENDHT_PROXY_SERVER=ON ';
else
options+='-DOPENDHT_PROXY_SERVER=OFF ';
fi
if [[ "$OPENDHT_TEST_JOB" == *"opendht.proxyclient"* ]] || [[ "$OPENDHT_TEST_JOB" == *"opendht.push"* ]]; then
options+='-DOPENDHT_PROXY_CLIENT=ON ';
else
options+='-DOPENDHT_PROXY_CLIENT=OFF ';
fi
if [[ "$OPENDHT_TEST_JOB" == *"opendht.push"* ]]; then
options+='-DOPENDHT_PUSH_NOTIFICATIONS=ON ';
else
options+='-DOPENDHT_PUSH_NOTIFICATIONS=OFF ';
fi
docker run opendht-proxy /bin/sh -c "cd /root/opendht && mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX=/usr -DOPENDHT_PYTHON=ON -DOPENDHT_LTO=ON -DOPENDHT_TESTS=ON $options .. && make -j8 && ./opendht_unit_tests && make install";
fi
- |
# llvm build
if [[ "$OPENDHT_TEST_JOB" == *"opendht.llvm"* ]]; then
docker build -f docker/DockerfileTravisLlvm .
fi
notifications:
email:
+114 -9
View File
@@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 3.1)
project (opendht)
set (opendht_VERSION_MAJOR 1)
set (opendht_VERSION_MINOR 3.6)
set (opendht_VERSION_MINOR 7.1)
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
set (PACKAGE_VERSION ${opendht_VERSION})
set (VERSION "${opendht_VERSION}")
@@ -15,9 +15,20 @@ 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)
option (OPENDHT_INDEX "Build DHT indexation feature" OFF)
option (OPENDHT_TESTS "Add unit tests executable" 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 +47,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 +87,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 +110,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
@@ -95,9 +121,14 @@ list (APPEND opendht_SOURCES
src/node.cpp
src/value.cpp
src/dht.cpp
src/op_cache.cpp
src/storage.h
src/listener.h
src/search.h
src/value_cache.h
src/op_cache.h
src/net.h
src/parsed_message.h
src/request.h
src/callbacks.cpp
src/routing_table.cpp
@@ -105,7 +136,6 @@ list (APPEND opendht_SOURCES
src/network_engine.cpp
src/securedht.cpp
src/dhtrunner.cpp
src/indexation/pht.cpp
src/log.cpp
)
@@ -120,20 +150,70 @@ 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
include/opendht/log.h
include/opendht/log_enable.h
include/opendht.h
include/opendht/indexation/pht.h
)
if (OPENDHT_INDEX)
list (APPEND opendht_SOURCES src/indexation/pht.cpp)
list (APPEND opendht_HEADERS include/opendht/indexation/pht.h)
add_definitions(-DOPENDHT_INDEXATION)
endif()
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_HEADERS
include/opendht/proxy.h
)
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 +247,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 +260,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 ()
@@ -219,3 +299,28 @@ install (DIRECTORY include DESTINATION ${CMAKE_INSTALL_PREFIX})
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/opendht.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
install (EXPORT opendht DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/opendht FILE opendhtConfig.cmake)
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/opendhtConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/opendht)
# Unit tests
IF(OPENDHT_TESTS)
FIND_PACKAGE(Cppunit REQUIRED)
# unit testing
add_executable(opendht_unit_tests
tests/tests_runner.cpp
tests/infohashtester.h
tests/infohashtester.cpp
tests/dhtrunnertester.h
tests/dhtrunnertester.cpp
)
if (OPENDHT_SHARED)
TARGET_LINK_LIBRARIES(opendht_unit_tests opendht)
else ()
TARGET_LINK_LIBRARIES(opendht_unit_tests opendht-static)
endif ()
TARGET_LINK_LIBRARIES(opendht_unit_tests
${CMAKE_THREAD_LIBS_INIT}
${CPPUNIT_LIBRARIES}
${GNUTLS_LIBRARIES}
)
enable_testing()
add_test(TEST opendht_unit_tests)
ENDIF()
+14 -3
View File
@@ -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>
+79 -51
View File
@@ -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>
+4
View File
@@ -12,6 +12,10 @@ if USE_CYTHON
SUBDIRS += python
endif
if ENABLE_TESTS
SUBDIRS += tests
endif
SUBDIRS += doc
ACLOCAL_AMFLAGS = -I m4
+23 -14
View File
@@ -1,24 +1,29 @@
<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.
A lightweight C++11 Distributed Hash Table implementation.
* Light and fast C++11 Kademlia DHT library.
* Distributed shared key->value data-store
* Clean and powerful distributed map API with storage of arbitrary binary values of up to 56 KB.
* Optional public key cryptography layer providing data signature and encryption (using GnuTLS).
* IPv4 and IPv6 support.
* Python binding.
OpenDHT provides an easy to use distributed in-memory data store.
Every node in the network can read and write values to the store.
Values are distributed over the network, with redundancy.
* Lightweight and scalable, designed for large networks and small devices
* High resilience to network disruption
* Public key cryptography layer providing optional data signature and encryption (using GnuTLS)
* IPv4 and IPv6 support
* Clean and powerful C++11 map API
* Python 3 bindings
* REST API
## Documentation
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,23 +96,27 @@ 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 4.8+ (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, Windows with MinGW), Clang/LLVM (GNU/Linux, Android, macOS, iOS).
- Build tested with Microsoft Visual Studio 2015
## Contact
IRC: join us on Freenode at [`#opendht`](https://webchat.freenode.net/?channels=%23opendht).
## Licence
Copyright (C) 2014-2017 Savoir-faire Linux Inc.
## License
Copyright (C) 2014-2018 Savoir-faire Linux Inc.
OpenDHT 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.
See COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html for the full GPLv3 licence.
See COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html for the full GPLv3 license.
This project is independent from another project called OpenDHT (Sean Rhea. Ph.D. Thesis, 2005), now extinct.
## Acknowledgements
This project was originally based on https://github.com/jech/dht by Juliusz Chroboczek.
It is independent from another project called OpenDHT (Sean Rhea. Ph.D. Thesis, 2005), now extinct.
## Donations
We gratefully accept Bitcoin donations to support OpenDHT development at: `bitcoin:3EykSd1An888efq4Bq3KaV3hJ3JQ4FPnwm`.
+1 -1
Submodule argon2 updated: 6f5427fb67...670229c849.
+28
View File
@@ -0,0 +1,28 @@
INCLUDE(FindPkgConfig)
PKG_CHECK_MODULES(PC_CPPUNIT "cppunit")
FIND_PATH(CPPUNIT_INCLUDE_DIRS
NAMES cppunit/TestCase.h
HINTS ${PC_CPPUNIT_INCLUDE_DIR}
${CMAKE_INSTALL_PREFIX}/include
PATHS
/usr/local/include
/usr/include
)
FIND_LIBRARY(CPPUNIT_LIBRARIES
NAMES cppunit
HINTS ${PC_CPPUNIT_LIBDIR}
${CMAKE_INSTALL_PREFIX}/lib
${CMAKE_INSTALL_PREFIX}/lib64
PATHS
${CPPUNIT_INCLUDE_DIRS}/../lib
/usr/local/lib
/usr/lib
)
LIST(APPEND CPPUNIT_LIBRARIES ${CMAKE_DL_LIBS})
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(CPPUNIT DEFAULT_MSG CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS)
MARK_AS_ADVANCED(CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS)
+17
View File
@@ -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()
+64 -3
View File
@@ -1,7 +1,7 @@
dnl define macros
m4_define([opendht_major_version], 1)
m4_define([opendht_minor_version], 3)
m4_define([opendht_patch_version], 6)
m4_define([opendht_minor_version], 7)
m4_define([opendht_patch_version], 1)
m4_define([opendht_version],
[opendht_major_version.opendht_minor_version.opendht_patch_version])
@@ -25,13 +25,20 @@ AC_PROG_CXX
AM_PROG_AR
dnl Check for logs
AC_ARG_ENABLE([logs], [AS_HELP_STRING([--disable-logs], [Disble DHT logs])])
AC_ARG_ENABLE([logs], [AS_HELP_STRING([--disable-logs], [Disable DHT logs])])
AS_IF([test "x$enable_logs" != "xno"], [
AC_DEFINE([OPENDHT_LOG], [true], [Define if DHT logs are enabled])
], [
AC_DEFINE([OPENDHT_LOG], [false], [Define if DHT logs are enabled])
])
dnl Check for indexation
AC_ARG_ENABLE([indexation], [AS_HELP_STRING([--disable-indexation], [Disable DHT indexation])])
AM_CONDITIONAL(ENABLE_INDEXATION, test x$enable_indexation != "xno")
AS_IF([test "x$enable_indexation" != "xno"], [
AC_DEFINE([OPENDHT_INDEXATION], [1], [Define if DHT indexation is enabled])
])
dnl Check for Doxygen
AC_ARG_ENABLE([doc], AS_HELP_STRING([--enable-doc], [Enable documentation generation (doxygen)]))
AS_IF([test "x$enable_doc" = "xyes"], [
@@ -128,6 +135,59 @@ 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_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], [
PKG_CHECK_MODULES([CppUnit], [cppunit >= 1.12])
])
AC_CONFIG_FILES([doc/Doxyfile doc/Makefile])
dnl Configure setup.py if we build the python module
@@ -142,5 +202,6 @@ AM_COND_IF([USE_CYTHON], [
AC_CONFIG_FILES([Makefile
src/Makefile
tools/Makefile
tests/Makefile
opendht.pc])
AC_OUTPUT
+13
View File
@@ -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()
+17 -2
View File
@@ -1,3 +1,18 @@
FROM ubuntu:17.04
FROM ubuntu:16.04
MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
RUN apt-get update && apt-get install -y build-essential cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev libmsgpack-dev libargon2-0-dev cython3 python3-dev python3-setuptools && apt-get clean
RUN apt-get update && apt-get install -y build-essential cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev cython3 python3-dev libcppunit-dev libjsoncpp-dev libasio-dev libssl-dev python3-setuptools && apt-get clean
# build restbed from sources
RUN git clone --recursive https://github.com/corvusoft/restbed.git \
&& cd restbed && mkdir build && cd build \
&& cmake -DBUILD_TESTS=NO -DBUILD_EXAMPLES=NO -DBUILD_SSL=NO -DBUILD_SHARED=YES -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. \
&& make -j8 install \
&& cd .. && rm -rf restbed
#build msgpack from source
RUN wget https://github.com/msgpack/msgpack-c/releases/download/cpp-2.1.5/msgpack-2.1.5.tar.gz \
&& tar -xzf msgpack-2.1.5.tar.gz \
&& cd msgpack-2.1.5 && mkdir build && cd build \
&& cmake -DMSGPACK_CXX11=ON -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=/usr .. \
&& make -j8 && make install \
&& cd ../.. && rm -rf msgpack-2.1.5 msgpack-2.1.5.tar.gz
+19 -3
View File
@@ -1,7 +1,23 @@
FROM ubuntu:17.04
FROM ubuntu:16.04
MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
RUN apt-get update \
&& apt-get install -y llvm llvm-dev clang make cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev libmsgpack-dev libargon2-0-dev cython3 python3-dev python3-setuptools \
&& apt-get install -y llvm llvm-dev clang make cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev libmsgpack-dev libjsoncpp-dev libasio-dev cython3 python3-dev python3-setuptools libcppunit-dev \
&& apt-get remove -y gcc g++ && apt-get autoremove -y && apt-get clean
ENV CC cc
ENV CXX c++
ENV CXX c++
# build restbed from sources
RUN git clone --recursive https://github.com/corvusoft/restbed.git \
&& cd restbed && mkdir build && cd build \
&& cmake -DBUILD_TESTS=NO -DBUILD_EXAMPLES=NO -DBUILD_SSL=NO -DBUILD_SHARED=YES -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. \
&& make -j8 install \
&& cd .. && rm -rf restbed
#build msgpack from source
RUN wget https://github.com/msgpack/msgpack-c/releases/download/cpp-2.1.5/msgpack-2.1.5.tar.gz \
&& tar -xzf msgpack-2.1.5.tar.gz \
&& cd msgpack-2.1.5 && mkdir build && cd build \
&& cmake -DMSGPACK_CXX11=ON -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=/usr .. \
&& make -j8 && make install \
&& cd ../.. && rm -rf msgpack-2.1.5 msgpack-2.1.5.tar.gz
-6
View File
@@ -1,6 +0,0 @@
FROM aberaud/opendht-deps-llvm
MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
RUN git clone https://github.com/savoirfairelinux/opendht.git \
&& cd opendht && mkdir build && cd build \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DOPENDHT_PYTHON=On && make -j8 && make install \
&& cd ../.. && rm -rf opendht
+3 -2
View File
@@ -1,6 +1,7 @@
FROM aberaud/opendht-deps
MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
COPY . /root/opendht
RUN cd /root/opendht && mkdir build && cd build \
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DOPENDHT_PYTHON=On -DOPENDHT_LTO=On .. && make -j8 && make install \
&& cd ../.. && rm -rf opendht
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DOPENDHT_PYTHON=On -DOPENDHT_LTO=On -DOPENDHT_TESTS=ON .. \
&& make -j8 && ./opendht_unit_tests && make install
+3 -2
View File
@@ -1,6 +1,7 @@
FROM aberaud/opendht-deps-llvm
MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
COPY . /root/opendht
RUN cd /root/opendht && mkdir build && cd build \
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DOPENDHT_PYTHON=On .. && make -j8 && make install \
&& cd ../.. && rm -rf opendht
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DOPENDHT_PYTHON=On -DOPENDHT_TESTS=ON .. \
&& make -j8 && ./opendht_unit_tests && make install
+4
View File
@@ -0,0 +1,4 @@
FROM aberaud/opendht-deps
MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
COPY . /root/opendht
+5
View File
@@ -19,6 +19,11 @@
#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"
#ifdef OPENDHT_INDEXATION
#include "opendht/indexation/pht.h"
#endif
+20 -6
View File
@@ -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
};
/**
@@ -85,6 +98,7 @@ using ValuesExport = std::pair<InfoHash, Blob>;
using QueryCallback = std::function<bool(const std::vector<std::shared_ptr<FieldValueIndex>>& fields)>;
using GetCallback = std::function<bool(const std::vector<std::shared_ptr<Value>>& values)>;
using ValueCallback = std::function<bool(const std::vector<std::shared_ptr<Value>>& values, bool expired)>;
using GetCallbackSimple = std::function<bool(std::shared_ptr<Value> value)>;
using ShutdownCallback = std::function<void()>;
+10
View File
@@ -36,6 +36,10 @@ extern "C" {
#endif
namespace dht {
/**
* Contains all crypto primitives
*/
namespace crypto {
class OPENDHT_PUBLIC CryptoException : public std::runtime_error {
@@ -383,6 +387,7 @@ struct OPENDHT_PUBLIC Certificate {
/** Read certificate alternative names */
std::vector<std::pair<NameType, std::string>> getAltNames() const;
std::chrono::system_clock::time_point getActivation() const;
std::chrono::system_clock::time_point getExpiration() const;
/**
@@ -583,6 +588,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 +602,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.
+17 -33
View File
@@ -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;
};
+41 -55
View File
@@ -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,24 +98,11 @@ 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;
types.registerType(type);
}
const ValueType& getType(ValueType::Id type_id) const {
const auto& t_it = types.find(type_id);
return (t_it == types.end()) ? ValueType::USER_DATA : t_it->second;
return types.getType(type_id);
}
/**
@@ -168,8 +150,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,8 +230,16 @@ 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& key, GetCallbackSimple cb, Value::Filter f={}, Where w = {}) {
virtual size_t listen(const InfoHash&, ValueCallback, Value::Filter={}, Where={});
virtual size_t listen(const InfoHash& key, GetCallback cb, Value::Filter f={}, Where w={}) {
return listen(key, [cb](const std::vector<Sp<Value>>& vals, bool expired){
if (not expired)
return cb(vals);
return true;
}, std::forward<Value::Filter>(f), std::forward<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 +297,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 +327,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};
@@ -369,7 +357,7 @@ private:
uint64_t oldsecret {};
// registred types
std::map<ValueType::Id, ValueType> types;
TypeStore types;
// are we a bootstrap node ?
// note: Any running node can be used as a bootstrap node.
@@ -402,7 +390,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 +409,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&);
void storageChanged(const InfoHash& id, Storage& st, ValueStorage&, bool newValue);
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
@@ -481,7 +467,7 @@ private:
Sp<Search> search(const InfoHash& id, sa_family_t af, GetCallback = {}, QueryCallback = {}, DoneCallback = {}, Value::Filter = {}, Query q = {});
void announce(const InfoHash& id, sa_family_t af, Sp<Value> value, DoneCallback callback, time_point created=time_point::max(), bool permanent = false);
size_t listenTo(const InfoHash& id, sa_family_t af, GetCallback cb, Value::Filter f = Value::AllFilter(), const Sp<Query>& q = {});
size_t listenTo(const InfoHash& id, sa_family_t af, ValueCallback cb, Value::Filter f = Value::AllFilter(), const Sp<Query>& q = {});
/**
* Refill the search with good nodes if possible.
@@ -505,7 +491,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);
@@ -549,53 +535,53 @@ private:
*
* @param sr The search to execute its operations.
*/
void searchStep(Sp<Search> sr);
void searchStep(Sp<Search>);
void searchSynchedNodeListen(const Sp<Search>&, SearchNode&);
void dumpSearch(const Search& sr, std::ostream& out) const;
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);
};
+246
View File
@@ -0,0 +1,246 @@
/*
* 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 size_t listen(const InfoHash&, ValueCallback, Value::Filter={}, 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;
protected:
bool logFilerEnable_ {};
InfoHash logFiler_ {};
Logger DHT_LOG;
};
} // namespace dht
+377
View File
@@ -0,0 +1,377 @@
/*
* 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"
#include "proxy.h"
namespace restbed {
class Request;
}
namespace Json {
class Value;
}
namespace dht {
class SearchCache;
class OPENDHT_PUBLIC DhtProxyClient final : public DhtInterface {
public:
DhtProxyClient();
explicit DhtProxyClient(std::function<void()> loopSignal, const std::string& serverHost, const std::string& pushClientId = "");
virtual void setPushNotificationToken(const std::string& token) {
#if OPENDHT_PUSH_NOTIFICATIONS
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));
}
/**
* 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&, ValueCallback, Value::Filter={}, Where={});
virtual size_t listen(const InfoHash& key, GetCallback cb, Value::Filter f={}, Where w={}) {
return listen(key, [cb](const std::vector<Sp<Value>>& vals, bool expired){
if (not expired)
return cb(vals);
return true;
}, std::forward<Value::Filter>(f), std::forward<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));
}
virtual bool cancelListen(const InfoHash& key, size_t token);
/**
* Call linked callback with a push notification
* @param notification to process
*/
void pushNotificationReceived(const std::map<std::string, std::string>& notification);
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&);
/**
* Get data currently being put at the given hash with the given id.
*/
Sp<Value> getPut(const InfoHash&, const Value::Id&);
/**
* Stop any put/announce operation at the given location,
* for the value with the given id.
*/
bool cancelPut(const InfoHash&, const Value::Id&);
void pingNode(const sockaddr*, socklen_t, DoneCallbackSimple&& /*cb*/={}) { }
virtual void registerType(const ValueType& type) {
types.registerType(type);
}
const ValueType& getType(ValueType::Id type_id) const {
return types.getType(type_id);
}
std::vector<Sp<Value>> getLocal(const InfoHash& k, Value::Filter filter) const;
Sp<Value> getLocalById(const InfoHash& k, Value::Id id) const;
/**
* 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 {}; }
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) {
restartListeners();
}
void connectivityChanged() {
getProxyInfos();
restartListeners();
loopSignal_();
}
private:
/**
* 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, sa_family_t family);
SockAddr parsePublicAddress(const Json::Value& val);
void opFailed();
size_t doListen(const InfoHash& key, ValueCallback, Value::Filter);
bool doCancelListen(const InfoHash& key, size_t token);
void doPut(const InfoHash&, Sp<Value>, DoneCallback, time_point created, bool permanent);
/**
* 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 publicAddressV4_;
SockAddr publicAddressV6_;
InfoHash myid {};
// registred types
TypeStore types;
/**
* Store listen requests.
*/
struct Listener;
struct ProxySearch;
size_t listenerToken_ {0};
std::map<InfoHash, ProxySearch> searches_;
mutable std::mutex searchLock_;
/**
* 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();
/**
* Refresh a listen via a token
* @param token
*/
void resubscribe(const InfoHash& key, Listener& listener);
/**
* 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_ {};
const std::function<void()> loopSignal_;
#if OPENDHT_PUSH_NOTIFICATIONS
void fillBodyToGetToken(std::shared_ptr<restbed::Request> request, unsigned token = 0);
void getPushRequest(Json::Value&) const;
#endif // OPENDHT_PUSH_NOTIFICATIONS
bool isDestroying_ {false};
};
}
#endif // OPENDHT_PROXY_CLIENT
+273
View File
@@ -0,0 +1,273 @@
/*
* Copyright (C) 2017-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_SERVER
#pragma once
#include "def.h"
#include "sockaddr.h"
#include "infohash.h"
#include "scheduler.h"
#include "value.h"
#include "proxy.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;
struct ServerStats {
/** Current number of listen operations */
size_t listenCount;
/** Current number of permanent put operations */
size_t putCount;
/** Current number of push tokens with at least one listen operation */
size_t pushListenersCount;
/** Average requests per second */
double requestRate;
std::string toString() const {
std::ostringstream ss;
ss << "Listens: " << listenCount << " Puts: " << putCount << " PushListeners: " << pushListenersCount << std::endl;
ss << "Requests: " << requestRate << " per second." << std::endl;
return ss.str();
}
};
ServerStats getStats() const;
std::shared_ptr<DhtRunner> getNode() const { return dht_; }
/**
* 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);
/**
* 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);
void cancelPut(const InfoHash& key, Value::Id vid);
#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;
/**
* Remove finished listeners
* @param testSession if we remove the listener only if the session is closed
*/
void removeClosedListeners(bool testSession = true);
#if OPENDHT_PUSH_NOTIFICATIONS
/**
* Subscribe to push notifications for an iOS or Android device.
* Method: SUBSCRIBE "/{InfoHash: .*}"
* Body: {"key": "device_key", (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.
* @param session
*/
void subscribe(const std::shared_ptr<restbed::Session>& session);
/**
* Unsubscribe to push notifications for an iOS or Android device.
* Method: UNSUBSCRIBE "/{InfoHash: .*}"
* Body: {"key": "device_key", "token": x} where x if the token to cancel
* Return: nothing
* @param session
*/
void unsubscribe(const std::shared_ptr<restbed::Session>& session);
/**
* 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;
/**
* Remove a push listener between a client and a hash
* @param pushToken
* @param key
* @param clientId
*/
void cancelPushListen(const std::string& pushToken, const InfoHash& key, const std::string& clientId);
#endif //OPENDHT_PUSH_NOTIFICATIONS
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
std::thread server_thread {};
std::unique_ptr<restbed::Service> service_;
std::shared_ptr<DhtRunner> dht_;
std::mutex schedulerLock_;
std::condition_variable schedulerCv_;
Scheduler scheduler_;
std::thread schedulerThread_;
Sp<Scheduler::Job> printStatsJob_;
// 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;
};
std::vector<SessionToHashToken> currentListeners_;
std::mutex lockListener_;
std::atomic_bool stopListeners {false};
struct PermanentPut;
struct SearchPuts;
std::map<InfoHash, SearchPuts> puts_;
mutable std::atomic<size_t> requestNum_ {0};
mutable std::atomic<time_point> lastStatsReset_ {time_point::min()};
const std::string pushServer_;
#if OPENDHT_PUSH_NOTIFICATIONS
struct Listener;
struct PushListener;
std::mutex lockPushListeners_;
std::map<std::string, PushListener> pushListeners_;
proxy::ListenToken tokenPushNotif_ {0};
#endif //OPENDHT_PUSH_NOTIFICATIONS
};
}
#endif //OPENDHT_PROXY_SERVER
+99 -19
View File
@@ -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();
@@ -129,7 +136,15 @@ public:
query(hash, cb, bindDoneCb(done_cb), q);
}
std::future<size_t> listen(InfoHash key, GetCallback vcb, Value::Filter f = Value::AllFilter(), Where w = {});
std::future<size_t> listen(InfoHash key, ValueCallback vcb, Value::Filter f = Value::AllFilter(), Where w = {});
std::future<size_t> listen(InfoHash key, GetCallback cb, Value::Filter f={}, Where w={}) {
return listen(key, [cb](const std::vector<Sp<Value>>& vals, bool expired){
if (not expired)
return cb(vals);
return true;
}, std::forward<Value::Filter>(f), std::forward<Where>(w));
}
std::future<size_t> listen(const std::string& key, GetCallback vcb, Value::Filter f = Value::AllFilter(), Where w = {});
std::future<size_t> listen(InfoHash key, GetCallbackSimple cb, Value::Filter f = Value::AllFilter(), Where w = {}) {
return listen(key, bindGetCb(cb), f, w);
@@ -202,7 +217,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 +265,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,18 +309,13 @@ 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.
* @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(in_port_t port, const crypto::Identity identity, bool threaded = false, NetId network = 0) {
void run(in_port_t port = 4222, const crypto::Identity identity = {}, bool threaded = false, NetId network = 0) {
run(port, {
/*.dht_config = */{
/*.node_config = */{
@@ -316,7 +326,9 @@ public:
},
/*.id = */identity
},
/*.threaded = */threaded
/*.threaded = */threaded,
/*.proxy_server = */"",
/*.push_node_id = */""
});
}
void run(in_port_t port, Config config);
@@ -329,7 +341,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 +359,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 +380,38 @@ 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);
/* Proxy server mothods */
void forwardAllMessages(bool forward);
private:
static constexpr std::chrono::seconds BOOTSTRAP_PERIOD {10};
@@ -373,16 +423,41 @@ 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;
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 +480,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_;
};
}
+215 -139
View File
@@ -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 {
inline int 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);
};
}
+4
View File
@@ -24,6 +24,10 @@
#include <iostream>
namespace dht {
/**
* Logging-related functions
*/
namespace log {
/**
+1 -1
View File
@@ -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
-82
View File
@@ -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 */
+40 -106
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
* Copyright (C) 2014-2018 Savoir-faire Linux Inc.
* Author(s) : Adrien Béraud <adrien.beraud@savoirfairelinux.com>
* Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
*
@@ -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,22 @@ private:
struct ParsedMessage;
/**
* Answer for a request.
*/
struct RequestAnswer {
Blob ntoken {};
Value::Id vid {};
std::vector<Sp<Value>> values {};
std::vector<Value::Id> refreshed_values {};
std::vector<Value::Id> expired_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 +116,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 +132,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 +171,7 @@ private:
std::function<RequestAnswer(Sp<Node>,
const InfoHash&,
const Blob&,
uint32_t,
Tid,
const Query&)> onListen {};
/**
* Called on announce request.
@@ -206,12 +201,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 +233,16 @@ 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);
void tellListenerRefreshed(Sp<Node> n, Tid socket_id, const InfoHash& hash, const Blob& ntoken, const std::vector<Value::Id>& values);
void tellListenerExpired(Sp<Node> n, Tid socket_id, const InfoHash& hash, const Blob& ntoken, const std::vector<Value::Id>& values);
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 +383,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 +421,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 +441,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 +455,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 +470,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 +495,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 +510,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
View File
@@ -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_;
};
}
+10 -5
View File
@@ -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;
};
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2017-2018 Savoir-faire Linux Inc.
* Author: 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 <chrono>
namespace dht {
namespace proxy {
constexpr const std::chrono::minutes OP_TIMEOUT {1 * 10}; // one hour
constexpr const std::chrono::minutes OP_MARGIN {5}; // 5 minutes
constexpr const char* const HTTP_PROTO {"http://"};
using ListenToken = uint64_t;
}
}
+5 -5
View File
@@ -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();
}
+16
View File
@@ -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.
*/
+7 -7
View File
@@ -36,11 +36,10 @@ namespace dht {
*/
class Scheduler {
public:
Scheduler(const Logger& l) : DHT_LOG(l) {}
struct Job {
Job(std::function<void()>&& f) : do_(std::move(f)) {}
std::function<void()> do_;
void cancel() { do_ = {}; }
};
/**
@@ -58,17 +57,19 @@ public:
return job;
}
void add(const Sp<Scheduler::Job>& job, time_point t) {
if (t != time_point::max())
timers.emplace(std::move(t), job);
}
/**
* Reschedules a job.
*
* @param time The time at which the job shall be rescheduled.
* @param job The job to edit.
*
* @return pointer to the newly scheduled job.
* @param t The time at which the job shall be rescheduled.
*/
void edit(Sp<Scheduler::Job>& job, time_point t) {
if (not job) {
DHT_LOG.ERR("editing an empty job");
return;
}
// std::function move doesn't garantee to leave the object empty.
@@ -118,7 +119,6 @@ public:
private:
time_point now {clock::now()};
std::multimap<time_point, Sp<Job>> timers {}; /* the jobs ordered by time */
const Logger& DHT_LOG;
};
}
+212 -18
View File
@@ -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,19 +89,17 @@ 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;
/**
* Will take ownership of the value, sign it using our private key and put it in the DHT.
*/
@@ -135,11 +145,195 @@ 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, ValueCallback, Value::Filter={}, Where={});
size_t listen(const InfoHash& key, GetCallback cb, Value::Filter = {}, Where w = {});
size_t listen(const InfoHash& key, GetCallbackSimple cb, Value::Filter f={}, Where w = {}) {
return listen(key, bindGetCb(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 forwardAllMessages(bool forward) {
forward_all_ = forward;
}
void setPushNotificationToken(const std::string& token = "") {
dht_->setPushNotificationToken(token);
}
/**
* Call linked callback with push_notification
* @param notification to process
*/
void pushNotificationReceived(const std::map<std::string, std::string>& notification) {
dht_->pushNotificationReceived(notification);
}
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;
Sp<Value> checkValue(const Sp<Value>& v);
ValueCallback getCallbackFilter(ValueCallback, Value::Filter&&);
GetCallback getCallbackFilter(GetCallback, Value::Filter&&);
Sp<crypto::PrivateKey> key_ {};
@@ -152,13 +346,13 @@ private:
std::map<InfoHash, Sp<crypto::Certificate>> nodesCertificates_ {};
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 +360,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
View File
@@ -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);
}
+21 -3
View File
@@ -32,6 +32,9 @@
#define WANT4 1
#define WANT6 2
/**
* OpenDHT C++ namespace
*/
namespace dht {
using NetId = uint32_t;
@@ -51,10 +54,22 @@ void erase_if(std::map<Key, Item>& map, const Condition& condition)
}
}
/**
* Split "[host]:port" or "host:port" to pair<"host", "port">.
*/
OPENDHT_PUBLIC std::pair<std::string, std::string>
splitPort(const std::string& s);
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 +108,9 @@ public:
// Serialization related definitions and utility functions
/**
* Arbitrary binary data.
*/
using Blob = std::vector<uint8_t>;
/**
+61 -6
View File
@@ -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;
}
@@ -104,6 +109,19 @@ struct OPENDHT_PUBLIC ValueType {
EditPolicy editPolicy {DEFAULT_EDIT_POLICY};
};
class TypeStore {
public:
void registerType(const ValueType& type) {
types[type.id] = type;
}
const ValueType& getType(ValueType::Id type_id) const {
const auto& t_it = types.find(type_id);
return (t_it == types.end()) ? ValueType::USER_DATA : t_it->second;
}
private:
std::map<ValueType::Id, ValueType> types {};
};
/**
* A "value" is data potentially stored on the Dht, with some metadata.
*
@@ -169,6 +187,15 @@ struct OPENDHT_PUBLIC Value
return f1(v) or f2(v);
};
}
std::vector<Sp<Value>> filter(const std::vector<Sp<Value>>& values) {
if (not (*this))
return values;
std::vector<Sp<Value>> ret;
for (const auto& v : values)
if ((*this)(v))
ret.emplace_back(v);
return ret;
}
};
/* Sneaky functions disguised in classes */
@@ -345,6 +372,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 +451,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 +470,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);
}
}
@@ -795,6 +842,10 @@ struct OPENDHT_PUBLIC Where
return ss.str();
}
bool empty() const {
return filters_.empty();
}
OPENDHT_PUBLIC friend std::ostream& operator<<(std::ostream& s, const dht::Where& q);
private:
@@ -945,6 +996,10 @@ unpackVector(const std::vector<std::shared_ptr<Value>>& vals) {
return ret;
}
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
uint64_t unpackId(const Json::Value& json, const std::string& key);
#endif
}
MSGPACK_ADD_ENUM(dht::Value::Field)
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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)
+26 -5
View File
@@ -9,6 +9,11 @@ libopendht_la_SOURCES = \
listener.h \
request.h \
search.h \
value_cache.h \
op_cache.h \
op_cache.cpp \
net.h \
parsed_message.h \
node_cache.cpp \
callbacks.cpp \
routing_table.cpp \
@@ -21,7 +26,6 @@ libopendht_la_SOURCES = \
securedht.cpp \
dhtrunner.cpp \
default_types.cpp \
indexation/pht.cpp \
log.cpp
if WIN32
@@ -36,7 +40,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 \
@@ -50,8 +53,27 @@ nobase_include_HEADERS = \
../include/opendht/default_types.h \
../include/opendht/log.h \
../include/opendht/log_enable.h \
../include/opendht/rng.h \
../include/opendht/indexation/pht.h
../include/opendht/rng.h
if ENABLE_PROXY_SERVER
libopendht_la_SOURCES += 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
if PROXY_CLIENT_OR_SERVER
libopendht_la_SOURCES += base64.h base64.cpp
nobase_include_HEADERS += ../include/opendht/proxy.h
endif
if ENABLE_INDEXATION
libopendht_la_SOURCES += indexation/pht.cpp
nobase_include_HEADERS += ../include/opendht/indexation/pht.h
endif
clean-local:
rm -rf libargon2.la
@@ -82,4 +104,3 @@ noinst_HEADERS = \
@top_builddir@/argon2/src/encoding.h \
@top_builddir@/argon2/src/thread.h
endif
+143
View File
@@ -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());
}
+36
View File
@@ -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);
+32
View File
@@ -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
}
+56 -7
View File
@@ -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()
{}
@@ -402,6 +411,8 @@ PublicKey::operator=(PublicKey&& o) noexcept
void
PublicKey::pack(Blob& b) const
{
if (not pk)
throw CryptoException(std::string("Could not export public key: null key"));
std::vector<uint8_t> tmp(2048);
size_t sz = tmp.size();
int err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_DER, tmp.data(), &sz);
@@ -428,6 +439,8 @@ PublicKey::unpack(const uint8_t* data, size_t data_size)
std::string
PublicKey::toString() const
{
if (not pk)
throw CryptoException(std::string("Could not print public key: null key"));
std::string ret;
size_t sz = ret.size();
int err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_PEM, (void*)ret.data(), &sz);
@@ -529,7 +542,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.");
@@ -831,6 +849,15 @@ Certificate::addRevocationList(std::shared_ptr<RevocationList> list)
revocation_lists.emplace(std::move(list));
}
std::chrono::system_clock::time_point
Certificate::getActivation() const
{
auto t = gnutls_x509_crt_get_activation_time(cert);
if (t == (time_t)-1)
return std::chrono::system_clock::time_point::min();
return std::chrono::system_clock::from_time_t(t);
}
std::chrono::system_clock::time_point
Certificate::getExpiration() const
{
@@ -882,6 +909,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)
{
@@ -890,8 +930,13 @@ Certificate::generate(const PrivateKey& key, const std::string& name, Identity c
return {};
Certificate ret {cert};
gnutls_x509_crt_set_activation_time(cert, time(NULL));
gnutls_x509_crt_set_expiration_time(cert, time(NULL) + (20 * 365 * 24 * 60 * 60));
int64_t now = time(NULL);
/* 2038 bug: don't allow time wrap */
auto boundTime = [](int64_t t) -> time_t {
return std::min<int64_t>(t, std::numeric_limits<time_t>::max());
};
gnutls_x509_crt_set_activation_time(cert, boundTime(now));
gnutls_x509_crt_set_expiration_time(cert, boundTime(now + (10 * 365 * 24 * 60 * 60)));
if (gnutls_x509_crt_set_key(cert, key.x509_key) != GNUTLS_E_SUCCESS) {
std::cerr << "Error when setting certificate key" << std::endl;
return {};
@@ -916,10 +961,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 +1309,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
View File
@@ -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;
}
+409 -481
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+845
View File
@@ -0,0 +1,845 @@
/*
* Copyright (C) 2017-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_SERVER
#include "dht_proxy_server.h"
#include "default_types.h"
#include "dhtrunner.h"
#include <msgpack.hpp>
#include <json/json.h>
#include <chrono>
#include <functional>
#include <limits>
using namespace std::placeholders;
namespace dht {
struct DhtProxyServer::PermanentPut {
time_point expiration;
std::string pushToken;
std::string clientId;
Sp<Scheduler::Job> expireJob;
Sp<Scheduler::Job> expireNotifyJob;
};
struct DhtProxyServer::SearchPuts {
std::map<dht::Value::Id, PermanentPut> puts;
};
constexpr const std::chrono::minutes PRINT_STATS_PERIOD {2};
DhtProxyServer::DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port , const std::string& pushServer)
: dht_(dht) , pushServer_(pushServer)
{
if (not dht_)
throw std::invalid_argument("A DHT instance must be provided");
// NOTE in c++14, use make_unique
service_ = std::unique_ptr<restbed::Service>(new restbed::Service());
std::cout << "Running DHT proxy server on port " << port << std::endl;
if (not pushServer.empty()) {
#if !OPENDHT_PUSH_NOTIFICATIONS
std::cerr << "Push server defined but built OpenDHT built without push notification support" << std::endl;
#else
std::cout << "Using push notification server: " << pushServer << 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", [this](const Sp<restbed::Session>& session) mutable { listen(session); } );
#if OPENDHT_PUSH_NOTIFICATIONS
resource->set_method_handler("SUBSCRIBE", [this](const Sp<restbed::Session>& session) mutable { subscribe(session); } );
resource->set_method_handler("UNSUBSCRIBE", [this](const Sp<restbed::Session>& session) mutable { unsubscribe(session); } );
#endif //OPENDHT_PUSH_NOTIFICATIONS
resource->set_method_handler("POST", [this](const Sp<restbed::Session>& session) mutable { put(session); });
#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);
lastStatsReset_ = clock::now();
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 (not service_->is_up() and not stopListeners) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
while (service_->is_up() and not stopListeners) {
removeClosedListeners();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// Remove last listeners
removeClosedListeners(false);
});
schedulerThread_ = std::thread([this]() {
while (not service_->is_up() and not stopListeners) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
printStatsJob_ = scheduler_.add(scheduler_.time() + PRINT_STATS_PERIOD, [this]{
if (service_->is_up() and not stopListeners) {
std::cout << getStats().toString() << std::endl;
scheduler_.edit(printStatsJob_, scheduler_.time() + PRINT_STATS_PERIOD);
}
});
while (service_->is_up() and not stopListeners) {
std::unique_lock<std::mutex> lock(schedulerLock_);
auto next = scheduler_.run();
if (next == time_point::max())
schedulerCv_.wait(lock);
else
schedulerCv_.wait_until(lock, next);
}
});
dht->forwardAllMessages(true);
}
DhtProxyServer::~DhtProxyServer()
{
stop();
}
void
DhtProxyServer::stop()
{
if (printStatsJob_)
printStatsJob_->cancel();
service_->stop();
{
std::lock_guard<std::mutex> lock(lockListener_);
auto listener = currentListeners_.begin();
while (listener != currentListeners_.end()) {
listener->session->close();
++ listener;
}
}
stopListeners = true;
schedulerCv_.notify_all();
// listenThreads_ will stop because there is no more sessions
if (listenThread_.joinable())
listenThread_.join();
if (schedulerThread_.joinable())
schedulerThread_.join();
if (server_thread.joinable())
server_thread.join();
}
DhtProxyServer::ServerStats
DhtProxyServer::getStats() const
{
ServerStats ret {};
auto now = clock::now();
auto last = lastStatsReset_.exchange(now);
auto count = requestNum_.exchange(0);
auto dt = std::chrono::duration<double>(now - last);
ret.requestRate = count / dt.count();
#if OPENDHT_PUSH_NOTIFICATIONS
ret.pushListenersCount = pushListeners_.size();
#endif
ret.putCount = puts_.size();
ret.listenCount = currentListeners_.size();
return ret;
}
void
DhtProxyServer::getNodeInfo(const std::shared_ptr<restbed::Session>& session) const
{
requestNum_++;
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*/)
{
try {
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\"}");
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
void
DhtProxyServer::get(const std::shared_ptr<restbed::Session>& session) const
{
requestNum_++;
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* */)
{
try {
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\"}");
}
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
void
DhtProxyServer::listen(const std::shared_ptr<restbed::Session>& session)
{
requestNum_++;
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 {
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\"}");
}
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
#if OPENDHT_PUSH_NOTIFICATIONS
struct DhtProxyServer::Listener {
std::shared_ptr<proxy::ListenToken> token;
std::string clientId;
std::future<size_t> internalToken;
Sp<Scheduler::Job> expireJob;
Sp<Scheduler::Job> expireNotifyJob;
};
struct DhtProxyServer::PushListener {
std::map<InfoHash, std::vector<Listener>> listeners;
bool isAndroid;
};
void
DhtProxyServer::subscribe(const std::shared_ptr<restbed::Session>& session)
{
requestNum_++;
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) mutable
{
try {
std::string err;
Json::Value root;
Json::CharReaderBuilder rbuilder;
auto* char_data = reinterpret_cast<const char*>(b.data());
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (!reader->parse(char_data, char_data + b.size(), &root, &err)) {
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
return;
}
auto pushToken = root["key"].asString();
if (pushToken.empty()) return;
auto tokenFromReq = unpackId(root, "token");
auto platform = root["platform"].asString();
auto isAndroid = platform == "android";
auto clientId = root.isMember("client_id") ? root["client_id"].asString() : std::string();
std::cout << "Subscribe " << infoHash << " token:" << tokenFromReq << " client:" << clientId << std::endl;
{
std::lock(schedulerLock_, lockListener_);
std::lock_guard<std::mutex> lk1(lockListener_, std::adopt_lock);
std::lock_guard<std::mutex> lk2(schedulerLock_, std::adopt_lock);
scheduler_.syncTime();
auto timeout = scheduler_.time() + proxy::OP_TIMEOUT;
// Check if listener is already present and refresh timeout if launched
// One push listener per pushToken.infoHash.clientId
auto pushListener = pushListeners_.emplace(pushToken, PushListener{}).first;
auto listeners = pushListener->second.listeners.emplace(infoHash, std::vector<Listener>{}).first;
for (auto& listener: listeners->second) {
if (listener.clientId == clientId) {
*listener.token = tokenFromReq;
scheduler_.edit(listener.expireJob, timeout);
scheduler_.edit(listener.expireNotifyJob, timeout - proxy::OP_MARGIN);
s->close(restbed::OK, "{\"token\": " + std::to_string(tokenFromReq) + "}\n");
schedulerCv_.notify_one();
return;
}
}
listeners->second.emplace_back(Listener{});
auto& listener = listeners->second.back();
listener.clientId = clientId;
// New listener
pushListener->second.isAndroid = isAndroid;
// The listener is not found, so add it.
auto token = std::make_shared<proxy::ListenToken>(tokenFromReq);
listener.token = token;
listener.internalToken = dht_->listen(infoHash,
[this, token, infoHash, pushToken, isAndroid, clientId](std::vector<std::shared_ptr<Value>> /*value*/) {
// Build message content.
Json::Value json;
json["key"] = infoHash.toString();
json["to"] = clientId;
json["token"] = std::to_string(*token);
sendPushNotification(pushToken, json, isAndroid);
return true;
}
);
listener.expireJob = scheduler_.add(timeout,
[this, clientId, infoHash, pushToken] {
cancelPushListen(pushToken, infoHash, clientId);
}
);
listener.expireNotifyJob = scheduler_.add(timeout - proxy::OP_MARGIN,
[this, token, infoHash, pushToken, isAndroid, clientId] {
std::cout << "Listener: sending refresh " << infoHash << std::endl;
Json::Value json;
json["timeout"] = infoHash.toString();
json["to"] = clientId;
json["token"] = std::to_string(*token);
sendPushNotification(pushToken, json, isAndroid);
}
);
}
schedulerCv_.notify_one();
s->close(restbed::OK, "{\"token\": " + std::to_string(tokenFromReq) + "}\n");
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
void
DhtProxyServer::unsubscribe(const std::shared_ptr<restbed::Session>& session)
{
requestNum_++;
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 {
std::string err;
Json::Value root;
Json::CharReaderBuilder rbuilder;
auto* char_data = reinterpret_cast<const char*>(b.data());
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (!reader->parse(char_data, char_data + b.size(), &root, &err)) {
s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
return;
}
auto pushToken = root["key"].asString();
if (pushToken.empty()) return;
auto clientId = root["client_id"].asString();
cancelPushListen(pushToken, infoHash, clientId);
s->close(restbed::OK);
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
void
DhtProxyServer::cancelPushListen(const std::string& pushToken, const dht::InfoHash& key, const std::string& clientId)
{
std::cout << "cancelPushListen: " << key << " clientId:" << clientId << std::endl;
std::lock_guard<std::mutex> lock(lockListener_);
auto pushListener = pushListeners_.find(pushToken);
if (pushListener == pushListeners_.end())
return;
auto listeners = pushListener->second.listeners.find(key);
if (listeners == pushListener->second.listeners.end())
return;
for (auto listener = listeners->second.begin(); listener != listeners->second.end();) {
if (listener->clientId == clientId) {
if (dht_)
dht_->cancelListen(key, std::move(listener->internalToken));
listener = listeners->second.erase(listener);
} else {
++listener;
}
}
if (listeners->second.empty()) {
pushListener->second.listeners.erase(listeners);
}
if (pushListener->second.listeners.empty()) {
pushListeners_.erase(pushListener);
}
}
void
DhtProxyServer::sendPushNotification(const std::string& token, const Json::Value& json, bool isAndroid) const
{
restbed::Uri uri(proxy::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;
notification["priority"] = "high";
notification["time_to_live"] = 600;
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, {});
}
#endif //OPENDHT_PUSH_NOTIFICATIONS
void
DhtProxyServer::cancelPut(const InfoHash& key, Value::Id vid)
{
std::cout << "cancelPut " << key << " " << vid << std::endl;
auto sPuts = puts_.find(key);
if (sPuts == puts_.end())
return;
auto& sPutsMap = sPuts->second.puts;
auto put = sPutsMap.find(vid);
if (put == sPutsMap.end())
return;
if (dht_)
dht_->cancelPut(key, vid);
if (put->second.expireNotifyJob)
put->second.expireNotifyJob->cancel();
sPutsMap.erase(put);
if (sPutsMap.empty())
puts_.erase(sPuts);
}
void
DhtProxyServer::put(const std::shared_ptr<restbed::Session>& session)
{
requestNum_++;
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 {
if (dht_) {
if(b.empty()) {
std::string response("{\"err\":\"Missing parameters\"}");
s->close(restbed::BAD_REQUEST, response);
} else {
std::string err;
Json::Value root;
Json::CharReaderBuilder rbuilder;
auto* char_data = reinterpret_cast<const char*>(b.data());
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (reader->parse(char_data, char_data + b.size(), &root, &err)) {
// Build the Value from json
auto value = std::make_shared<Value>(root);
bool permanent = root.isMember("permanent");
std::cout << "Got put " << infoHash << " " << *value << " " << (permanent ? "permanent" : "") << std::endl;
if (permanent) {
std::string pushToken, clientId, platform;
auto& pVal = root["permanent"];
if (pVal.isObject()) {
pushToken = pVal["key"].asString();
clientId = pVal["client_id"].asString();
platform = pVal["platform"].asString();
}
std::cout << "permanent: pushToken " << pushToken << " clientId " << clientId << " platform " << platform << std::endl;
bool isAndroid = platform == "android";
std::unique_lock<std::mutex> lock(schedulerLock_);
scheduler_.syncTime();
auto timeout = scheduler_.time() + proxy::OP_TIMEOUT;
auto vid = value->id;
auto sPuts = puts_.emplace(infoHash, SearchPuts{}).first;
auto r = sPuts->second.puts.emplace(vid, PermanentPut{});
auto& pput = r.first->second;
if (r.second) {
std::cout << "New ppush entry" << std::endl;
pput.expireJob = scheduler_.add(timeout, [this, infoHash, vid]{
std::cout << "Permanent put expired: " << infoHash << " " << vid << std::endl;
cancelPut(infoHash, vid);
});
#if OPENDHT_PUSH_NOTIFICATIONS
if (not pushToken.empty()) {
std::cout << "Adding refresh job" << std::endl;
pput.expireNotifyJob = scheduler_.add(timeout - proxy::OP_MARGIN,
[this, infoHash, vid, pushToken, clientId, isAndroid]
{
std::cout << "Permanent put refresh: " << infoHash << " " << std::to_string(vid) << " " << clientId << std::endl;
Json::Value json;
json["timeout"] = infoHash.toString();
json["to"] = clientId;
json["vid"] = std::to_string(vid);
sendPushNotification(pushToken, json, isAndroid);
});
}
#endif
} else {
std::cout << "Refreshing put jobs" << std::endl;
scheduler_.edit(pput.expireJob, timeout);
if (pput.expireNotifyJob)
scheduler_.edit(pput.expireNotifyJob, timeout - proxy::OP_MARGIN);
}
lock.unlock();
schedulerCv_.notify_one();
}
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\"}");
}
} catch (const std::exception& e) {
std::cout << "Error performing put: " << e.what() << std::endl;
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
#if OPENDHT_PROXY_SERVER_IDENTITY
void
DhtProxyServer::putSigned(const std::shared_ptr<restbed::Session>& session) const
{
requestNum_++;
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 {
if (dht_) {
if(b.empty()) {
std::string response("{\"err\":\"Missing parameters\"}");
s->close(restbed::BAD_REQUEST, response);
} else {
std::string err;
Json::Value root;
Json::CharReaderBuilder rbuilder;
auto* char_data = reinterpret_cast<const char*>(b.data());
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (reader->parse(char_data, char_data + b.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\"}");
}
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
void
DhtProxyServer::putEncrypted(const std::shared_ptr<restbed::Session>& session) const
{
requestNum_++;
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)
{
try {
if (dht_) {
if(b.empty()) {
std::string response("{\"err\":\"Missing parameters\"}");
s->close(restbed::BAD_REQUEST, response);
} else {
std::string err;
Json::Value root;
Json::CharReaderBuilder rbuilder;
auto* char_data = reinterpret_cast<const char*>(b.data());
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
bool parsingSuccessful = reader->parse(char_data, char_data + b.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\"}");
}
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
#endif // OPENDHT_PROXY_SERVER_IDENTITY
void
DhtProxyServer::handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const
{
requestNum_++;
#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
{
requestNum_++;
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* */)
{
try {
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\"}");
}
} catch (...) {
s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
}
}
);
}
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 = dht_ and (not testSession or listener->session->is_closed());
if (cancel) {
dht_->cancelListen(listener->hash, std::move(listener->token));
// Remove listener if unused
listener = currentListeners_.erase(listener);
} else {
++listener;
}
}
}
}
#endif //OPENDHT_PROXY_SERVER
+321 -137
View File
@@ -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
@@ -38,7 +43,19 @@ namespace dht {
constexpr std::chrono::seconds DhtRunner::BOOTSTRAP_PERIOD;
struct DhtRunner::Listener {
size_t tokenClassicDht;
size_t tokenProxyDht;
ValueCallback gcb;
InfoHash hash;
Value::Filter f;
Where w;
};
DhtRunner::DhtRunner() : dht_()
#if OPENDHT_PROXY_CLIENT
, dht_via_proxy_()
#endif //OPENDHT_PROXY_CLIENT
{
#ifdef _WIN32
WSADATA wsd;
@@ -58,42 +75,53 @@ 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_();
cv.wait_until(lk, wakeup, [this]() {
time_point wakeup;
try {
wakeup = loop_();
} catch (const dht::SocketException& e) {
startNetwork(local4, local6);
}
auto hasJobToDo = [this]() {
if (not running)
return true;
{
@@ -110,13 +138,21 @@ DhtRunner::run(const sockaddr_in* local4, const sockaddr_in6* local6, DhtRunner:
return true;
}
return false;
});
};
if (wakeup == time_point::max())
cv.wait(lk, hasJobToDo);
else
cv.wait_until(lk, wakeup, hasJobToDo);
}
});
}
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 +163,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 +181,9 @@ DhtRunner::join()
}
{
std::lock_guard<std::mutex> lck(dht_mtx);
dht_.reset();
resetDht();
status4 = NodeStatus::Disconnected;
status6 = NodeStatus::Disconnected;
bound4 = {};
bound6 = {};
}
}
@@ -156,23 +191,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 +246,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 +282,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 +298,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 +384,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 +399,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 +427,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 +490,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 +520,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 +544,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 {
@@ -497,13 +556,34 @@ void DhtRunner::query(const InfoHash& hash, QueryCallback cb, DoneCallback done_
}
std::future<size_t>
DhtRunner::listen(InfoHash hash, GetCallback vcb, Value::Filter f, Where w)
DhtRunner::listen(InfoHash hash, ValueCallback vcb, Value::Filter f, Where w)
{
auto ret_token = std::make_shared<std::promise<size_t>>();
{
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, bool expired){
if (not vcb(vals, expired)) {
#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,23 +599,41 @@ 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);
pending_ops.emplace([=](SecureDht& dht) {
#if OPENDHT_PROXY_CLIENT
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
dht.cancelListen(h, token);
#endif // OPENDHT_PROXY_CLIENT
});
cv.notify_all();
}
void
DhtRunner::cancelListen(InfoHash h, std::shared_future<size_t> token)
DhtRunner::cancelListen(InfoHash h, std::shared_future<size_t> ftoken)
{
{
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops.emplace([=](SecureDht& dht) {
auto tk = token.get();
dht.cancelListen(h, tk);
auto token = ftoken.get();
#if OPENDHT_PROXY_CLIENT
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
dht.cancelListen(h, token);
#endif // OPENDHT_PROXY_CLIENT
});
}
cv.notify_all();
@@ -660,7 +758,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 +789,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 +806,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 +828,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 +869,115 @@ 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
}
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
}
/**
* 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::pushNotificationReceived(const std::map<std::string, std::string>& data)
{
#if OPENDHT_PROXY_CLIENT && OPENDHT_PUSH_NOTIFICATIONS
{
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([=](SecureDht& dht) {
if (dht_via_proxy_)
dht_via_proxy_->pushNotificationReceived(data);
});
}
cv.notify_all();
#endif
}
}
+23 -91
View File
@@ -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;
}
+11 -5
View File
@@ -20,6 +20,7 @@
#include "value.h"
#include "utils.h"
#include "callbacks.h"
namespace dht {
@@ -27,14 +28,12 @@ namespace dht {
* Foreign nodes asking for updates about an InfoHash.
*/
struct Listener {
size_t sid;
time_point time;
Query query;
Listener(size_t sid, time_point t, Query&& q) : sid(sid), time(t), query(std::move(q)) {}
Listener(time_point t, Query&& q) : time(t), query(std::move(q)) {}
void refresh(size_t s, time_point t, Query&& q) {
sid = s;
void refresh(time_point t, Query&& q) {
time = t;
query = std::move(q);
}
@@ -46,7 +45,14 @@ struct Listener {
struct LocalListener {
Sp<Query> query;
Value::Filter filter;
GetCallback get_cb;
ValueCallback get_cb;
};
struct SearchListener {
Sp<Query> query;
Value::Filter filter;
ValueCallback get_cb;
};
}
+38
View File
@@ -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 */
+273 -507
View File
File diff suppressed because it is too large. Load diff
+72 -23
View File
@@ -20,6 +20,7 @@
#include "node.h"
#include "request.h"
#include "rng.h"
#include <sstream>
@@ -29,6 +30,13 @@ 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_()
{
thread_local 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 +49,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 +60,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 +75,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 +120,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
View File
@@ -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();
}
}
+204
View File
@@ -0,0 +1,204 @@
/*
* Copyright (C) 2018 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 "op_cache.h"
namespace dht {
constexpr const std::chrono::seconds OpCache::EXPIRATION;
bool
OpValueCache::onValuesAdded(const std::vector<Sp<Value>>& vals) {
std::vector<Sp<Value>> newValues;
for (const auto& v : vals) {
auto viop = values.emplace(v->id, OpCacheValueStorage{v});
if (viop.second) {
newValues.emplace_back(v);
} else {
viop.first->second.refCount++;
}
}
return newValues.empty() ? true : callback(newValues, false);
}
bool
OpValueCache::onValuesExpired(const std::vector<Sp<Value>>& vals) {
std::vector<Sp<Value>> expiredValues;
for (const auto& v : vals) {
auto vit = values.find(v->id);
if (vit != values.end()) {
vit->second.refCount--;
if (not vit->second.refCount) {
expiredValues.emplace_back(std::move(vit->second.data));
values.erase(vit);
}
}
}
return expiredValues.empty() ? true : callback(expiredValues, true);
}
std::vector<Sp<Value>>
OpValueCache::get(Value::Filter& filter) const {
std::vector<Sp<Value>> ret;
if (not filter)
ret.reserve(values.size());
for (const auto& v : values)
if (not filter or filter(*v.second.data))
ret.emplace_back(v.second.data);
return ret;
}
Sp<Value>
OpValueCache::get(Value::Id id) const {
auto v = values.find(id);
if (v == values.end())
return {};
return v->second.data;
}
std::vector<Sp<Value>>
OpValueCache::getValues() const {
std::vector<Sp<Value>> ret;
ret.reserve(values.size());
for (const auto& v : values)
ret.emplace_back(v.second.data);
return ret;
}
void
OpCache::onValuesAdded(const std::vector<Sp<Value>>& vals) {
if (not listeners.empty()) {
std::vector<LocalListener> list;
list.reserve(listeners.size());
for (const auto& l : listeners)
list.emplace_back(l.second);
for (auto& l : list)
l.get_cb(l.filter.filter(vals), false);
}
}
void
OpCache::onValuesExpired(const std::vector<Sp<Value>>& vals) {
if (not listeners.empty()) {
std::vector<LocalListener> list;
list.reserve(listeners.size());
for (const auto& l : listeners)
list.emplace_back(l.second);
for (auto& l : list)
l.get_cb(l.filter.filter(vals), true);
}
}
time_point
OpCache::getExpiration() const {
if (not listeners.empty())
return time_point::max();
return lastRemoved + EXPIRATION;
}
size_t
SearchCache::listen(ValueCallback get_cb, Sp<Query> q, Value::Filter filter, std::function<size_t(Sp<Query>, ValueCallback)> onListen)
{
// find exact match
auto op = ops.find(q);
if (op == ops.end()) {
// find satisfying query
for (auto it = ops.begin(); it != ops.end(); it++) {
if (q->isSatisfiedBy(*it->first)) {
op = it;
break;
}
}
}
if (op == ops.end()) {
// New query
op = ops.emplace(q, std::unique_ptr<OpCache>(new OpCache)).first;
auto& cache = *op->second;
cache.searchToken = onListen(q, [&](const std::vector<Sp<Value>>& values, bool expired){
return cache.onValue(values, expired);
});
}
auto token = nextToken_++;
if (nextToken_ == 0)
nextToken_++;
op->second->addListener(token, get_cb, q, filter);
return token;
}
bool
SearchCache::cancelListen(size_t gtoken, const time_point& now) {
for (auto& op : ops) {
if (op.second->removeListener(gtoken, now)) {
nextExpiration_ = std::min(nextExpiration_, op.second->getExpiration());
return true;
}
}
return false;
}
void
SearchCache::cancelAll(std::function<void(size_t)> onCancel) {
for (auto& op : ops) {
auto cache = std::move(op.second);
cache->removeAll();
onCancel(cache->searchToken);
}
ops.clear();
}
time_point
SearchCache::expire(const time_point& now, std::function<void(size_t)> onCancel) {
nextExpiration_ = time_point::max();
auto ret = nextExpiration_;
for (auto it = ops.begin(); it != ops.end();) {
auto expiration = it->second->getExpiration();
if (expiration < now) {
auto cache = std::move(it->second);
it = ops.erase(it);
onCancel(cache->searchToken);
} else {
nextExpiration_ = std::min(nextExpiration_, expiration);
ret = nextExpiration_;
++it;
}
}
return ret;
}
std::vector<Sp<Value>>
SearchCache::get(Value::Filter& filter) const {
if (ops.size() == 1)
return ops.begin()->second->get(filter);
std::map<Value::Id, Sp<Value>> c;
for (const auto& op : ops) {
for (const auto& v : op.second->get(filter))
c.emplace(v->id, v);
}
std::vector<Sp<Value>> ret;
ret.reserve(c.size());
for (auto& v : c)
ret.emplace_back(std::move(v.second));
return ret;
}
Sp<Value>
SearchCache::get(Value::Id id) const {
for (const auto& op : ops)
if (auto v = op.second->get(id))
return v;
return {};
}
}
+155
View File
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2018 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 "value.h"
#include "value_cache.h"
#include "listener.h"
namespace dht {
struct OpCacheValueStorage
{
Sp<Value> data {};
unsigned refCount {1};
OpCacheValueStorage(Sp<Value> val = {}) : data(val) {}
};
class OpValueCache {
public:
OpValueCache(ValueCallback&& cb) : callback(std::forward<ValueCallback>(cb)) {}
OpValueCache(OpValueCache&& o) : values(std::move(o.values)), callback(std::move(o.callback)) {
o.callback = {};
}
static ValueCallback cacheCallback(ValueCallback&& cb) {
auto cache = std::make_shared<OpValueCache>(std::forward<ValueCallback>(cb));
return [cache](const std::vector<Sp<Value>>& vals, bool expired){
return cache->onValue(vals, expired);
};
}
bool onValue(const std::vector<Sp<Value>>& vals, bool expired) {
if (expired)
return onValuesExpired(vals);
else
return onValuesAdded(vals);
}
bool onValuesAdded(const std::vector<Sp<Value>>& vals);
bool onValuesExpired(const std::vector<Sp<Value>>& vals);
std::vector<Sp<Value>> get(Value::Filter& filter) const;
Sp<Value> get(Value::Id id) const;
std::vector<Sp<Value>> getValues() const;
private:
OpValueCache(const OpValueCache&) = delete;
OpValueCache& operator=(const OpValueCache&) = delete;
std::map<Value::Id, OpCacheValueStorage> values {};
ValueCallback callback;
};
class OpCache {
public:
OpCache() : cache([this](const std::vector<Sp<Value>>& vals, bool expired){
if (expired)
onValuesExpired(vals);
else
onValuesAdded(vals);
return true;
}) {}
bool onValue(const std::vector<Sp<Value>>& vals, bool expired) {
cache.onValue(vals, expired);
return not listeners.empty();
}
void onValuesAdded(const std::vector<Sp<Value>>& vals);
void onValuesExpired(const std::vector<Sp<Value>>& vals);
void addListener(size_t token, ValueCallback cb, Sp<Query> q, Value::Filter filter) {
listeners.emplace(token, LocalListener{q, filter, cb});
cb(cache.get(filter), false);
}
bool removeListener(size_t token, const time_point& now) {
lastRemoved = now;
return listeners.erase(token) > 0;
}
void removeAll() {
listeners.clear();
}
bool isDone() {
return listeners.empty();
}
std::vector<Sp<Value>> get(Value::Filter& filter) const {
return cache.get(filter);
}
Sp<Value> get(Value::Id id) const {
return cache.get(id);
}
bool isExpired(const time_point& now) const {
return listeners.empty() and (lastRemoved + EXPIRATION < now);
}
time_point getExpiration() const;
size_t searchToken;
private:
constexpr static const std::chrono::seconds EXPIRATION {60};
OpCache(const OpCache&) = delete;
OpCache& operator=(const OpCache&) = delete;
OpValueCache cache;
std::map<size_t, LocalListener> listeners;
time_point lastRemoved {clock::now()};
};
class SearchCache {
public:
SearchCache() {}
SearchCache(SearchCache&&) = default;
size_t listen(ValueCallback get_cb, Sp<Query> q, Value::Filter filter, std::function<size_t(Sp<Query>, ValueCallback)> onListen);
bool cancelListen(size_t gtoken, const time_point& now);
void cancelAll(std::function<void(size_t)> onCancel);
time_point expire(const time_point& now, std::function<void(size_t)> onCancel);
time_point getExpiration() const {
return nextExpiration_;
}
std::vector<Sp<Value>> get(Value::Filter& filter) const;
Sp<Value> get(Value::Id id) const;
private:
SearchCache(const SearchCache&) = delete;
SearchCache& operator=(const SearchCache&) = delete;
std::map<Sp<Query>, std::unique_ptr<OpCache>> ops {};
size_t nextToken_ {1};
time_point nextExpiration_ {time_point::max()};
};
}
+313
View File
@@ -0,0 +1,313 @@
/*
* 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;
std::vector<Value::Id> refreshed_values {};
std::vector<Value::Id> expired_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();
}
} else if (auto raw_fields = findMapValue(req, "exp")) {
expired_values = raw_fields->as<decltype(expired_values)>();
} else if (auto raw_fields = findMapValue(req, "re")) {
refreshed_values = raw_fields->as<decltype(refreshed_values)>();
}
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
View File
@@ -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
View File
@@ -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;
}
}
+146 -143
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
* Copyright (C) 2014-2018 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
@@ -21,6 +21,8 @@
#include "value.h"
#include "request.h"
#include "listener.h"
#include "value_cache.h"
#include "op_cache.h"
namespace dht {
@@ -59,6 +61,17 @@ struct Dht::SearchNode {
*/
using SyncStatus = std::map<Sp<Query>, Sp<net::Request>>;
struct CachedListenStatus {
ValueCache cache;
Sp<Scheduler::Job> cacheExpirationJob {};
Sp<net::Request> req {};
CachedListenStatus(ValueStateCallback&& cb) : cache(std::forward<ValueStateCallback>(cb)) {}
CachedListenStatus(CachedListenStatus&&) = default;
CachedListenStatus(const CachedListenStatus&) = delete;
CachedListenStatus& operator=(const CachedListenStatus&) = delete;
};
using NodeListenerStatus = std::map<Sp<Query>, CachedListenStatus>;
Sp<Node> node {}; /* the node info */
/* queries sent for finding out values hosted by the node */
@@ -67,25 +80,44 @@ struct Dht::SearchNode {
std::map<Sp<Query>, std::vector<Sp<Query>>> pagination_queries {};
SyncStatus getStatus {}; /* get/sync status */
SyncStatus listenStatus {}; /* listen status */
NodeListenerStatus listenStatus {}; /* listen status */
AnnounceStatus acked {}; /* announcement status for a given value id */
Blob token {}; /* last token the node sent to us after a get request */
time_point last_get_reply {time_point::min()}; /* last time received valid token */
bool candidate {false}; /* A search node is candidate if the search is/was synced and this
node is a new candidate for inclusion. */
Sp<Scheduler::Job> syncJob {};
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 ?
*/
bool isSynced(time_point now) const {
bool isSynced(const time_point& now) const {
return not node->isExpired() and
not token.empty() and last_get_reply >= now - Node::NODE_EXPIRE_TIME;
}
time_point getSyncTime(const time_point& now) const {
if (node->isExpired() or token.empty())
return now;
return last_get_reply + Node::NODE_EXPIRE_TIME;
}
/**
* Could a particular "get" request be sent to this node now ?
*
@@ -173,6 +205,34 @@ struct Dht::SearchNode {
}
}
void cancelGet() {
for (const auto& status : getStatus) {
if (status.second->pending()) {
node->cancelRequest(status.second);
}
}
getStatus.clear();
}
void onValues(const Sp<Query>& q, net::RequestAnswer&& answer, const TypeStore& types, Scheduler& scheduler)
{
auto l = listenStatus.find(q);
if (l != listenStatus.end()) {
auto next = l->second.cache.onValues(answer.values,
answer.refreshed_values,
answer.expired_values, types, scheduler.time());
scheduler.edit(l->second.cacheExpirationJob, next);
}
}
void expireValues(const Sp<Query>& q, Scheduler& scheduler) {
auto l = listenStatus.find(q);
if (l != listenStatus.end()) {
auto next = l->second.cache.expireValues(scheduler.time());
scheduler.edit(l->second.cacheExpirationJob, next);
}
}
/**
* Tells if a request in the status map is expired.
*
@@ -180,12 +240,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,12 +254,20 @@ 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();
}
static bool pending(const NodeListenerStatus& status) {
return std::find_if(status.begin(), status.end(),
[](const NodeListenerStatus::value_type& r){
return r.second.req and r.second.req->pending();
}) != status.end();
}
bool pendingGet() const { return pending(getStatus); }
bool isAnnounced(Value::Id vid) const {
auto ack = acked.find(vid);
@@ -207,6 +275,15 @@ struct Dht::SearchNode {
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();
@@ -224,10 +301,22 @@ struct Dht::SearchNode {
else
return isListening(now, ls);
}
bool isListening(time_point now, SyncStatus::const_iterator listen_status) const {
bool isListening(time_point now, NodeListenerStatus::const_iterator listen_status) const {
if (listen_status == listenStatus.end())
return false;
return listen_status->second->reply_time + LISTEN_EXPIRE_TIME > now;
return listen_status->second.req->reply_time + LISTEN_EXPIRE_TIME > now;
}
void cancelListen() {
for (const auto& status : listenStatus)
node->cancelRequest(status.second.req);
listenStatus.clear();
}
void cancelListen(const Sp<Query>& query) {
auto it = listenStatus.find(query);
if (it != listenStatus.end()) {
node->cancelRequest(it->second.req);
listenStatus.erase(it);
}
}
/**
@@ -251,10 +340,10 @@ struct Dht::SearchNode {
*/
time_point getListenTime(const Sp<Query>& q) const {
auto listen_status = listenStatus.find(q);
if (listen_status == listenStatus.end() or not listen_status->second)
if (listen_status == listenStatus.end() or not listen_status->second.req)
return time_point::min();
return listen_status->second->pending() ? time_point::max() :
listen_status->second->reply_time + LISTEN_EXPIRE_TIME - REANNOUNCE_MARGIN;
return listen_status->second.req->pending() ? time_point::max() :
listen_status->second.req->reply_time + LISTEN_EXPIRE_TIME - REANNOUNCE_MARGIN;
}
/**
@@ -289,10 +378,16 @@ struct Dht::Search {
std::multimap<time_point, Get> callbacks {};
/* listeners */
std::map<size_t, LocalListener> listeners {};
std::map<size_t, SearchListener> listeners {};
size_t listener_token = 1;
/* Cache */
SearchCache cache;
Sp<Scheduler::Job> opExpirationJob;
~Search() {
if (opExpirationJob)
opExpirationJob->cancel();
for (auto& get : callbacks) {
get.second.done_cb(false, {});
get.second.done_cb = {};
@@ -319,7 +414,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;
}
@@ -377,11 +472,43 @@ struct Dht::Search {
done = true;
}
time_point getUpdateTime(time_point now) const;
bool isAnnounced(Value::Id id) const;
bool isListening(time_point now) const;
size_t listen(ValueCallback cb, Value::Filter f, const Sp<Query>& q, Scheduler& scheduler) {
//DHT_LOG.e(id, "[search %s IPv%c] listen", id.toString().c_str(), (af == AF_INET) ? '4' : '6');
return cache.listen(cb, q, f, [&](const Sp<Query>& q, ValueCallback vcb){
done = false;
auto token = ++listener_token;
listeners.emplace(token, SearchListener{q, f, vcb});
scheduler.edit(nextSearchStep, scheduler.time());
return token;
});
}
void cancelListen(size_t token, Scheduler& scheduler) {
cache.cancelListen(token, scheduler.time());
if (not opExpirationJob)
opExpirationJob = scheduler.add(time_point::max(), [this,&scheduler]{
auto nextExpire = cache.expire(scheduler.time(), [&](size_t t){
Sp<Query> query;
const auto& ll = listeners.find(t);
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);
}
});
scheduler.edit(opExpirationJob, nextExpire);
});
scheduler.edit(opExpirationJob, cache.getExpiration());
}
/**
* @return The number of non-good search nodes.
*/
@@ -401,26 +528,6 @@ struct Dht::Search {
return count;
}
/**
* Returns the time of the next "announce" event for this search,
* or time_point::max() if no such event is planned.
* Only makes sense when the search is synced.
*/
time_point getAnnounceTime(time_point now) const;
/**
* Returns the time of the next "listen" event for this search,
* or time_point::max() if no such event is planned.
* Only makes sense when the search is synced.
*/
time_point getListenTime(time_point now) const;
/**
* Returns the time of the next event for this search,
* or time_point::max() if no such event is planned.
*/
time_point getNextStepTime(time_point now) const;
/**
* Removes a node which have been expired for at least
* NODE::NODE_EXPIRE_TIME minutes. The search for an expired node starts
@@ -430,10 +537,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 +692,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)
@@ -662,36 +769,6 @@ Dht::Search::isDone(const Get& get) const
return true;
}
time_point
Dht::Search::getUpdateTime(time_point now) const
{
time_point ut = time_point::max();
const auto last_get = getLastGetTime();
unsigned i = 0, t = 0, d = 0;
const auto solicited_nodes = currentlySolicitedNodeCount();
for (const auto& sn : nodes) {
if (sn.node->isExpired() or (sn.candidate and t >= TARGET_NODES))
continue;
auto pending = sn.pending(sn.getStatus);
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)
ut = std::min(ut, now);
if (not sn.candidate)
d++;
} else
ut = std::min(ut, sn.last_get_reply + Node::NODE_EXPIRE_TIME);
t++;
if (not sn.candidate and ++i == TARGET_NODES)
break;
}
if (not callbacks.empty() and d == 0)
// If all synced/updated but some callbacks remain, step now to clear them
return now;
return ut;
}
bool
Dht::Search::isAnnounced(Value::Id id) const
{
@@ -718,7 +795,7 @@ Dht::Search::isListening(time_point now) const
for (const auto& n : nodes) {
if (n.isBad())
continue;
SearchNode::SyncStatus::const_iterator ls {};
SearchNode::NodeListenerStatus::const_iterator ls {};
for (ls = n.listenStatus.begin(); ls != n.listenStatus.end() ; ++ls) {
if (n.isListening(now, ls))
break;
@@ -731,78 +808,4 @@ Dht::Search::isListening(time_point now) const
return i;
}
time_point
Dht::Search::getAnnounceTime(time_point now) const
{
if (nodes.empty())
return time_point::max();
time_point ret {time_point::max()};
for (const auto& a : announce) {
if (!a.value) continue;
unsigned i = 0, t = 0;
for (const auto& n : nodes) {
if (not n.isSynced(now) or (n.candidate and t >= TARGET_NODES))
continue;
ret = std::min(ret, n.getAnnounceTime(a.value->id));
t++;
if (not n.candidate and ++i == TARGET_NODES)
break;
}
}
return ret;
}
time_point
Dht::Search::getListenTime(time_point now) const
{
if (listeners.empty())
return time_point::max();
time_point listen_time {time_point::max()};
unsigned i = 0, t = 0;
for (const auto& sn : nodes) {
if (not sn.isSynced(now) or (sn.candidate and t >= LISTEN_NODES))
continue;
for (auto& l : listeners)
listen_time = std::min(listen_time, sn.getListenTime(l.second.query));
t++;
if (not sn.candidate and ++i == LISTEN_NODES)
break;
}
return listen_time;
}
time_point
Dht::Search::getNextStepTime(time_point now) const
{
auto next_step = time_point::max();
if (expired or done)
return next_step;
auto ut = getUpdateTime(now);
if (ut != time_point::max()) {
//std::cout << id.toString() << " IPv" << (af==AF_INET?"4":"6") << " update time in " << print_dt(ut - now) << " s" << std::endl;
next_step = std::min(next_step, ut);
}
if (isSynced(now))
{
auto at = getAnnounceTime(now);
if (at != time_point::max()) {
//std::cout << id.toString() << " IPv" << (af==AF_INET?"4":"6") << " announce time in " << print_dt(at - now) << " s" << std::endl;
next_step = std::min(next_step, at);
}
auto lt = getListenTime(now);
if (lt != time_point::max()) {
//std::cout << id.toString() << " IPv" << (af==AF_INET?"4":"6") << " listen time in " << print_dt(lt - now) << " s" << std::endl;
next_step = std::min(next_step, lt);
}
}
return next_step;
}
}
+92 -62
View File
@@ -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) {
@@ -222,53 +213,84 @@ SecureDht::findPublicKey(const InfoHash& node, std::function<void(const Sp<const
findCertificate(node, [=](const Sp<crypto::Certificate> crt) {
if (crt && *crt) {
auto pk = std::make_shared<crypto::PublicKey>(crt->getPublicKey());
nodesPubKeys_[pk->getId()] = pk;
if (cb) cb(pk);
} else {
if (cb) cb(nullptr);
if (*pk) {
nodesPubKeys_[pk->getId()] = pk;
if (cb) cb(pk);
return;
}
}
if (cb) cb(nullptr);
});
}
Sp<Value>
SecureDht::checkValue(const Sp<Value>& v)
{
// Decrypt encrypted values
if (v->isEncrypted()) {
if (not key_) {
#if OPENDHT_PROXY_SERVER
if (forward_all_) // We are currently a proxy, send messages to clients.
return v;
#endif
return {};
}
try {
Value decrypted_val (decrypt(*v));
if (decrypted_val.recipient == getId()) {
if (decrypted_val.owner)
nodesPubKeys_[decrypted_val.owner->getId()] = decrypted_val.owner;
return std::make_shared<Value>(std::move(decrypted_val));
}
// Ignore values belonging to other people
} catch (const std::exception& e) {
DHT_LOG.WARN("Could not decrypt value %s : %s", v->toString().c_str(), e.what());
}
}
// Check signed values
else if (v->isSigned()) {
if (v->owner and v->owner->checkSignature(v->getToSign(), v->signature)) {
nodesPubKeys_[v->owner->getId()] = v->owner;
return v;
}
else
DHT_LOG.WARN("Signature verification failed for %s", v->toString().c_str());
}
// Forward normal values
else {
return v;
}
return {};
}
ValueCallback
SecureDht::getCallbackFilter(ValueCallback cb, Value::Filter&& filter)
{
return [=](const std::vector<Sp<Value>>& values, bool expired) {
std::vector<Sp<Value>> tmpvals {};
for (const auto& v : values) {
if (auto nv = checkValue(v))
if (not filter or filter(*nv))
tmpvals.emplace_back(std::move(nv));
}
if (cb and not tmpvals.empty())
return cb(tmpvals, expired);
return true;
};
}
GetCallback
SecureDht::getCallbackFilter(GetCallback cb, Value::Filter&& filter)
{
return [=](const std::vector<Sp<Value>>& values) {
std::vector<Sp<Value>> tmpvals {};
for (const auto& v : values) {
// Decrypt encrypted values
if (v->isEncrypted()) {
if (not key_)
continue;
try {
Value decrypted_val (decrypt(*v));
if (decrypted_val.recipient == getId()) {
nodesPubKeys_[decrypted_val.owner->getId()] = decrypted_val.owner;
if (not filter or filter(decrypted_val))
tmpvals.push_back(std::make_shared<Value>(std::move(decrypted_val)));
}
// Ignore values belonging to other people
} catch (const std::exception& e) {
DHT_LOG.WARN("Could not decrypt value %s : %s", v->toString().c_str(), e.what());
}
}
// Check signed values
else if (v->isSigned()) {
if (v->owner and v->owner->checkSignature(v->getToSign(), v->signature)) {
nodesPubKeys_[v->owner->getId()] = v->owner;
if (not filter or filter(*v))
tmpvals.push_back(v);
}
else
DHT_LOG.WARN("Signature verification failed for %s", v->toString().c_str());
}
// Forward normal values
else {
if (not filter or filter(*v))
tmpvals.push_back(v);
}
if (auto nv = checkValue(v))
if (not filter or filter(*nv))
tmpvals.emplace_back(std::move(nv));
}
if (cb && not tmpvals.empty())
if (cb and not tmpvals.empty())
return cb(tmpvals);
return true;
};
@@ -277,13 +299,20 @@ 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, ValueCallback 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));
}
size_t
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));
}
void
@@ -291,11 +320,12 @@ SecureDht::putSigned(const InfoHash& hash, Sp<Value> val, DoneCallback callback,
{
if (val->id == Value::INVALID_ID) {
crypto::random_device rdev;
std::uniform_int_distribution<Value::Id> rand_id;
val->id = rand_id(rdev);
}
// 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 +347,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 +364,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)
+6 -4
View File
@@ -167,7 +167,7 @@ struct Storage {
StoreDiff remove(const InfoHash& id, Value::Id);
StoreDiff expire(const InfoHash& id, time_point now);
std::pair<ssize_t, std::vector<Sp<Value>>> expire(const InfoHash& id, time_point now);
private:
Storage(const Storage&) = delete;
@@ -245,7 +245,7 @@ Storage::clear()
return {-tot_size, -num_values, 0};
}
Storage::StoreDiff
std::pair<ssize_t, std::vector<Sp<Value>>>
Storage::expire(const InfoHash& id, time_point now)
{
// expire listeners
@@ -271,16 +271,18 @@ Storage::expire(const InfoHash& id, time_point now)
auto r = std::partition(values.begin(), values.end(), [&](const ValueStorage& v) {
return v.expiration > now;
});
ssize_t del_num = -std::distance(r, values.end());
std::vector<Sp<Value>> ret;
ret.reserve(std::distance(r, values.end()));
ssize_t size_diff {};
std::for_each(r, values.end(), [&](const ValueStorage& v) {
size_diff -= v.data->size();
if (v.store_bucket)
v.store_bucket->erase(id, *v.data, v.expiration);
ret.emplace_back(std::move(v.data));
});
total_size += size_diff;
values.erase(r, values.end());
return {size_diff, del_num, del_listen};
return {size_diff, std::move(ret)};
}
}
+79 -10
View File
@@ -27,6 +27,53 @@
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::pair<std::string, std::string>
splitPort(const std::string& s) {
if (s.empty())
return {};
if (s[0] == '[') {
std::size_t closure = s.find_first_of(']');
std::size_t found = s.find_last_of(':');
if (closure == std::string::npos)
return {s, ""};
if (found == std::string::npos or found < closure)
return {s.substr(1,closure-1), ""};
return {s.substr(1,closure-1), s.substr(found+1)};
}
std::size_t found = s.find_last_of(':');
std::size_t first = s.find_first_of(':');
if (found == std::string::npos or found != first)
return {s, ""};
return {s.substr(0,found), s.substr(found+1)};
}
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 +85,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 +98,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 +116,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 +134,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 +149,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 +161,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);
}
+94 -7
View File
@@ -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,84 @@ Value::msgpack_unpack_body(const msgpack::object& o)
}
}
#if OPENDHT_PROXY_SERVER || OPENDHT_PROXY_CLIENT
Value::Value(Json::Value& json)
{
id = Value::Id(unpackId(json, "id"));
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)
val["to"] = recipient.toString();
}
val["type"] = type;
val["data"] = base64_encode(data);
if (not user_type.empty())
val["utype"] = user_type;
}
return val;
}
uint64_t
unpackId(const Json::Value& json, const std::string& key) {
uint64_t ret = 0;
try {
if (json.isMember(key)) {
const auto& t = json[key];
if (t.isString()) {
ret = std::stoull(t.asString());
} else {
ret = t.asLargestUInt();
}
}
} catch (...) {}
return ret;
}
#endif //OPENDHT_PROXY_SERVER
bool
FieldValue::operator==(const FieldValue& vfd) const
{
@@ -257,9 +342,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 +448,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 +578,3 @@ std::ostream& operator<<(std::ostream& s, const dht::Where& where) {
}
+189
View File
@@ -0,0 +1,189 @@
/*
* Copyright (C) 2018 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 "value.h"
namespace dht {
using ValueStateCallback = std::function<void(const std::vector<Sp<Value>>&, bool)>;
using CallbackQueue = std::list<std::function<void()>>;
class ValueCache {
public:
ValueCache(ValueStateCallback&& cb) : callback(std::forward<ValueStateCallback>(cb)) {}
ValueCache(ValueCache&& o) : values(std::move(o.values)), callback(std::move(o.callback)) {
o.callback = {};
}
~ValueCache() {
auto q = clear();
for (auto& cb: q)
cb();
}
CallbackQueue clear() {
std::vector<Sp<Value>> expired_values;
expired_values.reserve(values.size());
for (const auto& v : values)
expired_values.emplace_back(std::move(v.second.data));
values.clear();
CallbackQueue ret;
if (not expired_values.empty() and callback) {
auto cb = callback;
ret.emplace_back([expired_values, cb]{
cb(expired_values, true);
});
}
return ret;
}
time_point expireValues(const time_point& now) {
time_point ret = time_point::max();
auto cbs = expireValues(now, ret);
while (not cbs.empty()) {
cbs.front()();
cbs.pop_front();
}
return ret;
}
CallbackQueue expireValues(const time_point& now, time_point& next) {
std::vector<Sp<Value>> expired_values;
for (auto it = values.begin(); it != values.end();) {
if (it->second.expiration < now) {
expired_values.emplace_back(std::move(it->second.data));
it = values.erase(it);
} else {
next = std::min(next, it->second.expiration);
++it;
}
}
while (values.size() >= MAX_VALUES) {
// too many values, remove oldest values
time_point oldest_creation = time_point::max();
auto oldest_value = values.end();
for (auto it = values.begin(); it != values.end(); ++it)
if (it->second.created < oldest_creation) {
oldest_value = it;
oldest_creation = it->second.created;
}
if (oldest_value != values.end()) {
expired_values.emplace_back(std::move(oldest_value->second.data));
values.erase(oldest_value);
}
}
CallbackQueue ret;
if (not expired_values.empty() and callback) {
auto cb = callback;
ret.emplace_back([cb, expired_values]{
if (cb) cb(expired_values, true);
});
}
return ret;
}
time_point onValues
(const std::vector<Sp<Value>>& values,
const std::vector<Value::Id>& refreshed_values,
const std::vector<Value::Id>& expired_values,
const TypeStore& types, const time_point& now)
{
CallbackQueue cbs;
time_point ret = time_point::max();
if (not values.empty())
cbs.splice(cbs.end(), addValues(values, types, now));
for (const auto& vid : refreshed_values)
refreshValue(vid, types, now);
for (const auto& vid : expired_values)
cbs.splice(cbs.end(), expireValue(vid));
cbs.splice(cbs.end(), expireValues(now, ret));
while (not cbs.empty()) {
cbs.front()();
cbs.pop_front();
}
return ret;
}
private:
// prevent copy
ValueCache(const ValueCache&) = delete;
ValueCache& operator=(const ValueCache&) = delete;
ValueCache& operator=(ValueCache&&) = delete;
/* The maximum number of values we store in the cache. */
static constexpr unsigned MAX_VALUES {1024};
struct CacheValueStorage {
Sp<Value> data {};
time_point created {};
time_point expiration {};
CacheValueStorage() {}
CacheValueStorage(const Sp<Value>& v, time_point t, time_point e)
: data(v), created(t), expiration(e) {}
};
std::map<Value::Id, CacheValueStorage> values;
ValueStateCallback callback;
CallbackQueue addValues(const std::vector<Sp<Value>>& new_values, const TypeStore& types, const time_point& now) {
std::vector<Sp<Value>> nvals;
for (const auto& value : new_values) {
auto v = values.find(value->id);
if (v == values.end()) {
// new value
nvals.emplace_back(value);
values.emplace(value->id, CacheValueStorage(value, now, now + types.getType(value->type).expiration));
} else {
// refreshed value
v->second.created = now;
v->second.expiration = now + types.getType(v->second.data->type).expiration;
}
}
auto cb = callback;
CallbackQueue ret;
if (not nvals.empty())
ret.emplace_back([cb, nvals]{
if (cb) cb(nvals, false);
});
return ret;
}
CallbackQueue expireValue(Value::Id vid) {
auto v = values.find(vid);
if (v == values.end())
return {};
const std::vector<Sp<Value>> val {std::move(v->second.data)};
values.erase(v);
auto cb = callback;
CallbackQueue ret;
ret.emplace_back([cb, val]{
if (cb) cb(val, true);
});
return ret;
}
void refreshValue(Value::Id vid, const TypeStore& types, const time_point& now) {
auto v = values.find(vid);
if (v == values.end())
return;
v->second.created = now;
v->second.expiration = now + types.getType(v->second.data->type).expiration;
}
};
}
+9
View File
@@ -0,0 +1,9 @@
if ENABLE_TESTS
bin_PROGRAMS = opendht_unit_tests
AM_CPPFLAGS = -I../include
nobase_include_HEADERS = infohashtester.h dhtrunnertester.h
opendht_unit_tests_SOURCES = tests_runner.cpp infohashtester.cpp dhtrunnertester.cpp
opendht_unit_tests_LDFLAGS = -lopendht -lcppunit -L@top_builddir@/src/.libs @GnuTLS_LIBS@
endif
+64
View File
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: 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 "dhtrunnertester.h"
// std
#include <iostream>
#include <string>
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(DhtRunnerTester);
void
DhtRunnerTester::setUp() {
node1.run(42222, {}, true);
node2.run(42232, {}, true);
node2.bootstrap(node1.getBound());
}
void
DhtRunnerTester::tearDown() {
node1.join();
node2.join();
}
void
DhtRunnerTester::testConstructors() {
CPPUNIT_ASSERT(node1.getBoundPort() == 42222);
CPPUNIT_ASSERT(node2.getBoundPort() == 42232);
}
void
DhtRunnerTester::testGetPut() {
auto key = dht::InfoHash::get("123");
dht::Value val {"hey"};
auto val_data = val.data;
node1.put(key, std::move(val));
auto vals = node2.get(key).get();
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testListen() {
// TODO
}
} // namespace test
+62
View File
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: 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
// cppunit
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <opendht/dhtrunner.h>
namespace test {
class DhtRunnerTester : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(DhtRunnerTester);
CPPUNIT_TEST(testConstructors);
CPPUNIT_TEST(testGetPut);
CPPUNIT_TEST(testListen);
CPPUNIT_TEST_SUITE_END();
dht::DhtRunner node1 {};
dht::DhtRunner node2 {};
public:
/**
* Method automatically called before each test by CppUnit
*/
void setUp();
/**
* Method automatically called after each test CppUnit
*/
void tearDown();
/**
* Test the differents behaviors of constructors
*/
void testConstructors();
/**
* Test get and put methods
*/
void testGetPut();
/**
* Test listen method
*/
void testListen();
};
} // namespace test
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (C) 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/>.
*/
#include "infohashtester.h"
// std
#include <iostream>
#include <string>
// opendht
#include "opendht/infohash.h"
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(InfoHashTester);
void
InfoHashTester::setUp() {
}
void
InfoHashTester::testConstructors() {
// Default constructor creates a null infohash
auto nullHash = dht::InfoHash();
CPPUNIT_ASSERT(nullHash.size() == 20);
CPPUNIT_ASSERT(!nullHash);
// Build from a uint8_t. if length to short, should get a null infohash
uint8_t to_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
auto infohash = dht::InfoHash(to_short, 8);
CPPUNIT_ASSERT(infohash.size() == 20);
CPPUNIT_ASSERT_EQUAL(infohash.toString(),
std::string("0000000000000000000000000000000000000000"));
// Build from a uint8_t. if length is enough, data should contains the uint8_t
uint8_t enough[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
infohash = dht::InfoHash(enough, 20);
CPPUNIT_ASSERT(infohash.size() == 20);
const auto* data = infohash.data();
for (auto i = 0; i < 20; ++i) {
CPPUNIT_ASSERT_EQUAL(enough[i], data[i]);
}
// if too long, should be cutted to 20
uint8_t tooLong[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb0};
infohash = dht::InfoHash(tooLong, 21);
CPPUNIT_ASSERT(infohash.size() == 20);
const auto* data2 = infohash.data();
for (auto i = 0; i < 20; ++i) {
CPPUNIT_ASSERT_EQUAL(enough[i], data2[i]);
}
// Build from string
auto infohashFromStr = dht::InfoHash("0102030405060708090A0102030405060708090A");
CPPUNIT_ASSERT(infohashFromStr.size() == 20);
const auto* dataStr = infohashFromStr.data();
for (auto i = 0; i < 20; ++i) {
CPPUNIT_ASSERT_EQUAL((int)dataStr[i], (int)data[i]);
}
}
void
InfoHashTester::testComperators() {
auto nullHash = dht::InfoHash();
auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
// operator ==
CPPUNIT_ASSERT_EQUAL(minHash, minHash);
CPPUNIT_ASSERT_EQUAL(minHash, dht::InfoHash("0000000000000000000000000000000000111110"));
CPPUNIT_ASSERT(!(minHash == maxHash));
// operator !=
CPPUNIT_ASSERT(!(minHash != minHash));
CPPUNIT_ASSERT(!(minHash != dht::InfoHash("0000000000000000000000000000000000111110")));
CPPUNIT_ASSERT(minHash != maxHash);
// operator<
CPPUNIT_ASSERT(nullHash < minHash);
CPPUNIT_ASSERT(nullHash < maxHash);
CPPUNIT_ASSERT(minHash < maxHash);
CPPUNIT_ASSERT(!(minHash < nullHash));
CPPUNIT_ASSERT(!(maxHash < nullHash));
CPPUNIT_ASSERT(!(maxHash < minHash));
CPPUNIT_ASSERT(!(minHash < minHash));
// bool()
CPPUNIT_ASSERT(maxHash);
CPPUNIT_ASSERT(!nullHash);
}
void
InfoHashTester::testLowBit() {
auto nullHash = dht::InfoHash();
auto minHash = dht::InfoHash("0000000000000000000000000000000000000010");
auto maxHash = dht::InfoHash("0100000000000000000000000000000000000000");
CPPUNIT_ASSERT_EQUAL(nullHash.lowbit(), -1);
CPPUNIT_ASSERT_EQUAL(minHash.lowbit(), 155);
CPPUNIT_ASSERT_EQUAL(maxHash.lowbit(), 7);
}
void
InfoHashTester::testCommonBits() {
auto nullHash = dht::InfoHash();
auto minHash = dht::InfoHash("0000000000000000000000000000000000000010");
auto maxHash = dht::InfoHash("0100000000000000000000000000000000000000");
CPPUNIT_ASSERT_EQUAL(dht::InfoHash::commonBits(nullHash, nullHash), (unsigned)160);
CPPUNIT_ASSERT_EQUAL(dht::InfoHash::commonBits(nullHash, minHash), (unsigned)155);
CPPUNIT_ASSERT_EQUAL(dht::InfoHash::commonBits(nullHash, maxHash), (unsigned)7);
CPPUNIT_ASSERT_EQUAL(dht::InfoHash::commonBits(minHash, maxHash), (unsigned)7);
}
void
InfoHashTester::testXorCmp() {
auto nullHash = dht::InfoHash();
auto minHash = dht::InfoHash("0000000000000000000000000000000000000010");
auto maxHash = dht::InfoHash("0100000000000000000000000000000000000000");
CPPUNIT_ASSERT_EQUAL(minHash.xorCmp(nullHash, maxHash), -1);
CPPUNIT_ASSERT_EQUAL(minHash.xorCmp(maxHash, nullHash), 1);
CPPUNIT_ASSERT_EQUAL(minHash.xorCmp(minHash, maxHash), -1);
CPPUNIT_ASSERT_EQUAL(minHash.xorCmp(maxHash, minHash), 1);
CPPUNIT_ASSERT_EQUAL(nullHash.xorCmp(minHash, maxHash), -1);
CPPUNIT_ASSERT_EQUAL(nullHash.xorCmp(maxHash, minHash), 1);
// Because hashes are circular in distance.
CPPUNIT_ASSERT_EQUAL(maxHash.xorCmp(nullHash, minHash), -1);
CPPUNIT_ASSERT_EQUAL(maxHash.xorCmp(minHash, nullHash), 1);
}
void
InfoHashTester::tearDown() {
}
} // namespace test
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright (C) 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/>.
*/
#pragma once
// cppunit
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class InfoHashTester : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(InfoHashTester);
CPPUNIT_TEST(testConstructors);
CPPUNIT_TEST(testComperators);
CPPUNIT_TEST(testLowBit);
CPPUNIT_TEST(testCommonBits);
CPPUNIT_TEST(testXorCmp);
CPPUNIT_TEST_SUITE_END();
public:
/**
* Method automatically called before each test by CppUnit
*/
void setUp();
/**
* Method automatically called after each test CppUnit
*/
void tearDown();
/**
* Test the differents behaviors of constructors
*/
void testConstructors();
/**
* Test compare operators
*/
void testComperators();
/**
* Test lowbit method
*/
void testLowBit();
/**
* Test commonBits method
*/
void testCommonBits();
/**
* Test xorCmp operators
*/
void testXorCmp();
};
} // namespace test
+35
View File
@@ -0,0 +1,35 @@
/*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/CompilerOutputter.h>
#include <iostream>
int main(int argc, char** argv) {
CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
CppUnit::Test *suite = registry.makeTest();
if (suite->countTestCases() == 0) {
std::cout << "No test cases specified for suite" << std::endl;
return 1;
}
CppUnit::TextUi::TestRunner runner;
runner.addTest(suite);
auto result = runner.run() ? 0 : 1;
return result;
}
+9 -4
View File
@@ -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;
}
+190 -38
View File
@@ -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,23 +56,56 @@ 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
<< " rs [token] Resubscribe to opendht." << std::endl
<< " rp [token] Inject a push notification in Opendht." << std::endl
#else
<< " stt [server_address] Start the proxy client." << 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
<< " cpp <key> <id> Cancel persistent put operation for <key> and value <id>." << 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;
<< " e <key> <dest> <str> Put string value at <key>, encrypted for <dest> with its public key (if found)." << std::endl
<< " cc Trigger connectivity changed signal." << std::endl;
#ifdef OPENDHT_INDEXATION
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
<< " ii <name> <key> <value> Inserts the value <value> under the key <key> in the index named <name>." << std::endl
<< std::endl;
#endif
}
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;
@@ -81,7 +115,9 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
using_history();
#endif
#ifdef OPENDHT_INDEXATION
std::map<std::string, indexation::Pht> indexes;
#endif
while (true)
{
@@ -91,7 +127,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") {
@@ -105,6 +141,12 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
std::cout << dht->getNodesStats(AF_INET).toString() << std::endl;
std::cout << "IPv6 stats:" << std::endl;
std::cout << dht->getNodesStats(AF_INET6).toString() << std::endl;
#if OPENDHT_PROXY_SERVER
for (const auto& proxy : proxies) {
std::cout << "Stats for proxy on port " << proxy.first << std::endl;
std::cout << " " << proxy.second->getStats().toString() << std::endl;
}
#endif
continue;
} else if (op == "lr") {
std::cout << "IPv4 routing table:" << std::endl;
@@ -115,20 +157,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;
@@ -140,11 +180,8 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
iss >> idstr;
try {
auto addr = splitPort(idstr);
if (not addr.first.empty() and addr.second.empty()){
std::stringstream ss;
ss << DHT_DEFAULT_PORT;
addr.second = ss.str();
}
if (not addr.first.empty() and addr.second.empty())
addr.second = std::to_string(DHT_DEFAULT_PORT);
dht->bootstrap(addr.first.c_str(), addr.second.c_str());
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
@@ -160,19 +197,66 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
log::disableLogging(*dht);
dht->setLogFilter(filter);
continue;
} else if (op == "cc") {
dht->connectivityChanged();
}
#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, std::unique_ptr<DhtProxyServer>(new DhtProxyServer(dht, port, pushServer)));
#else
proxies.emplace(port, std::unique_ptr<DhtProxyServer>(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") {
dht->enableProxy(true);
continue;
} else if (op == "stp") {
dht->enableProxy(false);
continue;
}
#if OPENDHT_PUSH_NOTIFICATIONS
else if (op == "rp") {
iss >> value;
dht->pushNotificationReceived({{"to", "dhtnode"}, {"token", value}});
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") {
if (false) {}
#ifdef OPENDHT_INDEXATION
else if (op == "il" or op == "ii") {
// Pht syntax
iss >> index >> keystr;
auto new_index = std::find_if(indexes.begin(), indexes.end(),
@@ -180,7 +264,7 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
return i.first == index;
}) == indexes.end();
if (not index.size()) {
std::cout << "You must enter the index name." << std::endl;
std::cerr << "You must enter the index name." << std::endl;
continue;
} else if (new_index) {
using namespace dht::indexation;
@@ -194,15 +278,21 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
} catch (std::invalid_argument& e) { std::cout << e.what() << std::endl; }
}
}
#endif
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 +326,26 @@ 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) {
std::cout << "Listen: found value:" << std::endl;
std::cout << "\t" << *value << std::endl;
auto token = dht->listen(id, [](const std::vector<std::shared_ptr<Value>>& values, bool expired) {
std::cout << "Listen: found " << values.size() << " values" << (expired ? " expired" : "") << std::endl;
for (const auto& value : values)
std::cout << "\t" << *value << std::endl;
return true;
}, {}, dht::Where {std::move(rem)});
auto t = token.get();
std::cout << "Listening, token: " << t << std::endl;
}
if (op == "cl") {
std::string rem;
iss >> rem;
size_t token;
try {
token = std::stoul(rem);
} catch(...) {
std::cerr << "Syntax: cl [key] [token]" << std::endl;
continue;
}
dht->cancelListen(id, token);
}
else if (op == "p") {
std::string v;
@@ -253,6 +358,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();
@@ -292,6 +416,7 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
std::cout << "Announce: " << (ok ? "success" : "failure") << " (took " << print_dt(end-start) << "s)" << std::endl;
});
}
#ifdef OPENDHT_INDEXATION
else if (op == "il") {
std::string exact_match;
iss >> exact_match;
@@ -337,6 +462,7 @@ void cmd_loop(std::shared_ptr<DhtRunner>& dht, dht_params& params)
catch (std::invalid_argument& e) { std::cout << e.what() << std::endl; }
catch (std::out_of_range& e) { }
}
#endif
}
std::cout << std::endl << "Stopping node..." << std::endl;
@@ -357,6 +483,7 @@ main(int argc, char **argv)
print_usage();
return 0;
}
if (params.daemonize) {
daemonize();
} else if (params.service) {
@@ -365,11 +492,21 @@ main(int argc, char **argv)
dht::crypto::Identity crt {};
if (params.generate_identity) {
auto ca_tmp = dht::crypto::generateIdentity("DHT Node CA");
auto ca_tmp = dht::crypto::generateEcIdentity("DHT Node CA");
crt = dht::crypto::generateIdentity("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.node_config.maintain_storage = false;
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 +522,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, std::unique_ptr<DhtProxyServer>(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;
}
+1 -1
View File
@@ -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 {
+186
View File
@@ -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>
+31 -28
View File
@@ -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
@@ -44,29 +45,6 @@
#include <sstream>
#include <fstream>
/**
* Split "[host]:port" or "host:port" to pair<"host", "port">.
*/
std::pair<std::string, std::string>
splitPort(const std::string& s) {
if (s.empty())
return {};
if (s[0] == '[') {
std::size_t closure = s.find_first_of(']');
std::size_t found = s.find_last_of(':');
if (closure == std::string::npos)
return {s, ""};
if (found == std::string::npos or found < closure)
return {s.substr(1,closure-1), ""};
return {s.substr(1,closure-1), s.substr(found+1)};
}
std::size_t found = s.find_last_of(':');
std::size_t first = s.find_first_of(':');
if (found == std::string::npos or found != first)
return {s, ""};
return {s.substr(0,found), s.substr(found+1)};
}
/*
* The mapString shall have the following format:
*
@@ -89,6 +67,7 @@ std::map<std::string, std::string> parseStringMap(std::string mapString) {
return map;
}
#ifdef OPENDHT_INDEXATION
dht::indexation::Pht::Key createPhtKey(std::map<std::string, std::string> pht_key_str_map) {
dht::indexation::Pht::Key pht_key;
for (auto f : pht_key_str_map) {
@@ -97,11 +76,10 @@ dht::indexation::Pht::Key createPhtKey(std::map<std::string, std::string> pht_ke
}
return pht_key;
}
#endif
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 +99,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 +116,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,11 +137,28 @@ 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;
case 'b':
params.bootstrap = splitPort((optarg[0] == '=') ? optarg+1 : optarg);
params.bootstrap = dht::splitPort((optarg[0] == '=') ? optarg+1 : optarg);
if (not params.bootstrap.first.empty() and params.bootstrap.second.empty()) {
params.bootstrap.second = std::to_string(DHT_DEFAULT_PORT);
}