Compare commits

..

159 Commits

Author SHA1 Message Date
EinMByte
45d27f8ddc Merge pull request #281 from anonimal/master
Catch exception in SSUServer that would dump core.
2015-10-14 11:05:43 +02:00
anonimal
6ae5f20ec1 Catch exception in SSUServer that would dump core. 2015-10-13 20:26:32 +00:00
EinMByte
e4f19716dd Merge pull request #278 from anonimal/master
Minor enhancements.
2015-10-05 16:40:02 +02:00
anonimal
dd88e13b95 English corrections. 2015-10-05 11:44:48 +00:00
anonimal
7c924e13d9 Add Arch Linux support to documentation. 2015-10-05 10:33:30 +00:00
anonimal
65b5a6773e Add Netbeans-related to .gitignore 2015-10-05 10:24:22 +00:00
EinMByte
9da8f44761 Merge pull request #275 from majestrate/master
add datagram forwarding to sam
2015-09-30 22:03:01 +02:00
jeff
2248d2ad82 get rid of compiler warnings, add logging 2015-09-30 09:18:30 -04:00
jeff
569bd45f10 remove unused import 2015-09-30 07:58:53 -04:00
jeff
06a7612e2e Merge remote-tracking branch 'purple/master' 2015-09-30 07:55:11 -04:00
jeff
39cd25bb44 initial sam udp forward 2015-09-29 13:31:04 -04:00
EinMByte
d05ad68aa4 Merge pull request #273 from EinMByte/master
Fix #272 and #274.
2015-09-28 21:26:29 +02:00
EinMByte
4a69a0a831 Fix #274 (hack). 2015-09-28 09:46:29 +02:00
EinMByte
68cf69fabc Fix #272. 2015-09-27 22:41:37 +02:00
EinMByte
af66f335c9 Merge pull request #271 from EinMByte/master
Benchmarking, webui and windows build
2015-09-27 19:49:45 +02:00
EinMByte
b9e25f2c96 Remove orignal certificate. 2015-09-27 17:10:23 +02:00
EinMByte
73725774dc Minor bugfixes for windows. 2015-09-27 16:55:39 +02:00
EinMByte
c9080f9f72 Changes to static build (related to #270). 2015-09-27 14:45:24 +02:00
EinMByte
65252790e6 Fix #270. 2015-09-27 13:43:02 +02:00
EinMByte
36c77080b6 Fixes needed for windows build (issue #270). 2015-09-24 22:47:44 +02:00
EinMByte
99a4be498a Properly escape backslash in CMakeLists (windows). 2015-09-21 17:25:26 +02:00
EinMByte
633f71c145 Display outbound tunnels in webui. 2015-09-21 15:58:36 +02:00
EinMByte
0c2830b9a5 Fix webui tunnel status column. 2015-09-18 23:12:08 +02:00
EinMByte
f04f556b75 Add basic inbound tunnel information to webui. 2015-09-18 21:37:21 +02:00
EinMByte
19557a0908 Update doc/ files to include --install. 2015-09-18 14:26:05 +02:00
EinMByte
3ef89718a1 Added --install flag. 2015-09-18 14:19:06 +02:00
EinMByte
c741382fc9 Added 404 page to the webui. 2015-09-18 11:52:09 +02:00
EinMByte
dbade8b569 (Graceful) restart and reseed buttons in webui. 2015-09-17 23:18:31 +02:00
EinMByte
fbea1ea142 Remove obsolete includes and constants in HTTPServer 2015-09-17 17:18:36 +02:00
EinMByte
f05419845b Move part of BUILD_NOTES.md to BUILDING.md 2015-09-17 16:59:39 +02:00
EinMByte
9890bf98e4 Add more information to webui. 2015-09-17 16:49:55 +02:00
EinMByte
7e066f7f69 webui: reauthenticate when token expires 2015-09-17 16:21:11 +02:00
EinMByte
30798f835e Fixes to the webui CSS 2015-09-17 14:02:40 +02:00
EinMByte
221e350228 Move MIME type detection to util/HTTP.cpp 2015-09-17 11:59:04 +02:00
EinMByte
17cd149e7f Several improvements to HTTPServer. 2015-09-17 11:47:16 +02:00
EinMByte
1ec31125b0 Change WIN32 to _WIN32 in util.cpp. 2015-09-13 16:54:55 +02:00
EinMByte
0c376117b0 Simplify CSS for webui. 2015-09-12 14:43:30 +02:00
EinMByte
d3cede7995 Fix operating system detection in util.cpp. 2015-09-12 14:19:12 +02:00
EinMByte
98d8dd7224 Merge branch 'master' of https://github.com/PurpleI2P/i2pd 2015-09-11 18:58:40 +02:00
Mikal Villa
8ff01be841 Cleanup in README.md 2015-09-10 15:00:39 +08:00
Mikal Villa
01c11d5fa5 Sorted out the documentation 2015-09-10 02:09:56 +08:00
EinMByte
4d6d032115 Add more information to webui. 2015-09-07 13:41:48 +02:00
EinMByte
719bfbc89b Added basic webui (layout from l-n-s). 2015-09-07 12:31:57 +02:00
EinMByte
e7350a3af4 Started rewrite of HTTPServer. 2015-09-06 20:34:50 +02:00
EinMByte
3d30b4bbbc Add Response type to util::http. 2015-09-06 18:51:19 +02:00
EinMByte
e3b891de41 Add util::GetWebuiDataDir(), HTTP parsing utilities and tests. 2015-09-06 16:15:46 +02:00
EinMByte
6857dcb911 Make cmake copy webui files to the i2pd data folder. 2015-09-05 21:02:44 +02:00
EinMByte
c42d76bca7 Merge branch 'master' of https://github.com/PurpleI2P/i2pd 2015-09-05 11:41:03 +02:00
EinMByte
07adb8ff64 Merge pull request #265 from at676/master
Support OpenBSD.
2015-09-05 11:40:28 +02:00
at676
91c1643ffe Support OpenBSD. 2015-09-05 01:32:50 -07:00
EinMByte
0b1d6b3c01 Fix typos. 2015-08-30 19:07:26 +02:00
EinMByte
670f369659 Merge branch 'master' of https://github.com/PurpleI2P/i2pd 2015-08-30 19:04:28 +02:00
EinMByte
97e06f3f2b Add benchmarking code and optimization option. 2015-08-30 19:00:26 +02:00
EinMByte
ed96e5ede1 Merge pull request #263 from EinMByte/master
EdDSASigner/Verifier support, fix warnings
2015-08-30 14:16:15 +02:00
EinMByte
ec02a27a39 Update README.md 2015-08-30 13:34:30 +02:00
EinMByte
6dd36b8289 Fix more warnings (#260). 2015-08-30 12:33:09 +02:00
EinMByte
ee0d603a81 Fix remaining warnings (fixes #260). 2015-08-30 12:10:33 +02:00
EinMByte
48c66a1d08 Fix all unused parameter warnings (issue #260) 2015-08-30 12:02:09 +02:00
EinMByte
828ac404eb Do not include Signature.h in Identity.h. 2015-08-30 11:30:28 +02:00
EinMByte
a24d99d678 Use size_t and uint64_t instead of long long in ed25519/. 2015-08-30 10:18:29 +02:00
EinMByte
22f9380d49 Add constructor from keypair for EDDSA25519Signer. 2015-08-30 09:58:07 +02:00
EinMByte
eabf434a48 Add tests for EdDSA25519, fix Verifier. 2015-08-29 23:21:32 +02:00
EinMByte
99b2ca860d Add interface for edddsa key generation. 2015-08-29 21:53:04 +02:00
EinMByte
93d60152d5 Add eddsa from ref10 implementation (with some modifications). 2015-08-29 19:32:50 +02:00
EinMByte
2fcc91a755 Merge pull request #258 from EinMByte/master
Fix #253.
2015-08-26 22:23:58 +02:00
EinMByte
ab3b100402 Fix #253. 2015-08-26 22:17:10 +02:00
EinMByte
f0aa7b55be Merge pull request #257 from EinMByte/master
New build system
2015-08-25 20:42:42 +02:00
EinMByte
0979f0d2f2 Update README and BUILD_NOTES. 2015-08-21 23:35:26 +02:00
EinMByte
5d00ad6473 Fix building tests, move binaries to different output directory. 2015-08-21 23:27:25 +02:00
EinMByte
103b6db717 Better hardening flags. 2015-08-21 22:50:19 +02:00
EinMByte
8ac9520dfd Implement #243, separate core/client (PCH support dropped for now) 2015-08-21 22:29:33 +02:00
EinMByte
5db0448c6b Merge pull request #256 from EinMByte/master
Update .gitignore, fix typo in CMakeLists.txt.
2015-08-18 22:45:30 +02:00
EinMByte
bdaf2c16aa Update .gitignore, fix typo in CMakeLists.txt. 2015-08-18 22:17:28 +02:00
EinMByte
9236c7b391 Merge pull request #255 from apprb/dev
CMake: tests support
2015-08-18 21:53:52 +02:00
EinMByte
ff466b7aaa Merge pull request #254 from EinMByte/master
Fix #252.
2015-08-18 21:51:59 +02:00
apprb
e285cd034b CMake: tests support 2015-08-19 00:58:07 +06:00
EinMByte
ca84c71c39 Fix #252. 2015-08-18 10:58:24 +02:00
EinMByte
2ab1cf0a89 Merge pull request #251 from EinMByte/master
Changes to HTTPServer
2015-08-18 10:48:04 +02:00
EinMByte
485e2237e0 Update README in accordance with #245. 2015-08-18 10:45:40 +02:00
EinMByte
a2e6acbd44 Do not include iomanip in HTTPServer.cpp, fix typo. 2015-08-17 21:35:47 +02:00
EinMByte
64d3aa9c56 Remove broken link from web UI. 2015-08-17 12:23:21 +02:00
EinMByte
3922a203b7 Merge https://github.com/majestrate/i2pd into web-ui 2015-08-16 23:39:36 +02:00
EinMByte
3cc95c0df2 Merge branch 'master' into web-ui 2015-08-16 23:38:20 +02:00
EinMByte
823ae63308 Merge pull request #250 from edwtjo/make-tunnelscfg-configurable
Make tunnels.cfg configurable
2015-08-16 22:03:16 +02:00
EinMByte
6de4ea2dce Merge pull request #248 from tayler1/master
Returning default mtu.
2015-08-16 19:19:00 +02:00
Edward Tjörnhammar
3d489a2996 Make tunnels.cfg configurable 2015-08-11 23:19:25 +02:00
jeff
413d1b4960 make http server http/1.1 compliant 2015-08-11 16:55:55 -04:00
tayler1
e57b6294ae Returning default mtu.
Also mtu function for osx.
2015-08-11 22:36:35 +03:00
Riccardo Spagni
14c5753261 Merge pull request #247 from edwtjo/address-insteadof-host
Use address instead of host in code and README.md
2015-08-11 21:08:35 +02:00
Edward Tjörnhammar
56cb3004a7 Use address instead of host in code and README.md 2015-08-11 18:27:26 +02:00
EinMByte
86b83ca614 Merge pull request #242 from EinMByte/master
Tests for AES + Improvements to i2pcontrol
2015-08-03 09:25:30 +02:00
EinMByte
2db3fe36d1 Fix building with CMake (typo in commit 12dd8a3) 2015-08-02 23:22:13 +02:00
EinMByte
12dd8a394d Fix building without AESNI. 2015-08-02 23:18:55 +02:00
EinMByte
6ed48ab876 Get rid of old HTTPServer proxy functionality. 2015-08-02 23:04:52 +02:00
EinMByte
c3685927d2 Modfiy indention and spaces in I2PControlServer.h/cpp 2015-08-02 22:01:09 +02:00
EinMByte
999001733d Fix I2PControl invalid password error message. 2015-08-02 20:42:01 +02:00
EinMByte
79768cf783 Mention I2PControl password option in README. 2015-08-02 20:39:10 +02:00
EinMByte
843abb60dd Make i2pcontrol password configurable. 2015-08-02 19:59:16 +02:00
EinMByte
8434863beb Fixed typos (thx zzz) 2015-08-02 17:14:36 +02:00
EinMByte
c10d0fdb7e Make i2pcontrol error codes negative. 2015-08-02 17:02:53 +02:00
EinMByte
833a606b13 Add a constant for the I2PControl token size. 2015-08-02 16:38:46 +02:00
EinMByte
69d93146f2 Expire tokens, use std::shared_ptr for I2PControlSession. 2015-08-02 16:32:54 +02:00
EinMByte
109127a39e Generate random I2PControl tokens. 2015-08-02 14:46:39 +02:00
EinMByte
c7f4a79b2c Destroy I2PControlSession before io_service, cancel waiting operations. 2015-08-02 12:57:16 +02:00
EinMByte
9cca01d159 Error handling and authentication for I2PControl. 2015-08-01 23:10:10 +02:00
EinMByte
996c36b93d Fixes for I2PControl. 2015-08-01 19:19:09 +02:00
EinMByte
1f90b08445 Began separation of i2pcontrol (untested). 2015-08-01 16:48:35 +02:00
EinMByte
af4719d665 Move AESNIMacros.h to crypto/ 2015-07-31 13:11:47 +02:00
EinMByte
5d0b24d696 Add tests for AES CBC (mainly important for custom AESNI implementation). 2015-07-31 13:01:49 +02:00
EinMByte
59e081f41e Fix typo "Chipher" -> "Cipher" 2015-07-30 22:06:42 +02:00
Riccardo Spagni
d9dc406d18 Merge pull request #239 from EinMByte/master
Split source code in directories
2015-07-30 18:21:26 +02:00
EinMByte
9597917183 Split tunnel-specific crypto from aes.h/cpp. 2015-07-30 17:27:47 +02:00
EinMByte
d9bb09780f Created tunnel/ 2015-07-30 16:25:43 +02:00
EinMByte
c8190d0020 Move Log.h/cpp to util/ 2015-07-30 15:54:02 +02:00
EinMByte
6268e297e4 Fix building with cmake. 2015-07-29 18:37:15 +02:00
EinMByte
13e8945666 Moved I2PEndian to util/ 2015-07-28 22:48:38 +02:00
EinMByte
b9aebadd46 Add crypto/ directory. 2015-07-28 22:03:39 +02:00
EinMByte
9e5c33bce7 Fixed tests after introducing util/. 2015-07-28 19:30:44 +02:00
EinMByte
89d3d17b28 Move base64 to util/ 2015-07-28 19:27:27 +02:00
EinMByte
3668c27db6 Moved some files to util/ 2015-07-28 18:37:06 +02:00
EinMByte
581c59a195 Merge branch 'master' of https://github.com/PurpleI2P/i2pd 2015-07-27 22:24:27 +02:00
EinMByte
9876a2a019 Merge pull request #238 from notorca/master
Build with clang on linux fixed
2015-07-27 22:23:57 +02:00
EinMByte
18ca8b9b97 Move SSU and NTCP to transport/. 2015-07-27 22:17:31 +02:00
Cyril Lashkevich
25b8a18224 Build with clang on linux fixed 2015-07-27 22:31:25 +03:00
EinMByte
e90b0dd54f Update TODO. 2015-07-27 21:28:50 +02:00
EinMByte
3f3103572a Merge pull request #237 from EinMByte/master
Fixes for #235 and #233.
Also tested by psi, and seems to be working fine.
2015-07-27 18:46:15 +02:00
EinMByte
9c03be08da Do not build unit tests by default with make (#233). 2015-07-27 14:50:28 +02:00
EinMByte
e70f0d999f Include address option for tunnels in README.md. 2015-07-27 14:07:28 +02:00
EinMByte
524a02cb0d Make address for proxies, BOB, SAM, ... configurable (#235). 2015-07-27 13:40:35 +02:00
EinMByte
a7da2423ce Do not bind HTTPServer to the "any" address (#235). 2015-07-27 12:17:16 +02:00
Riccardo Spagni
668235c697 Merge pull request #230 from EinMByte/master
Base32/64 tests and documentation + GetMTU changes
2015-07-24 15:55:24 +02:00
EinMByte
7d38b1a9b9 Base64/32 tests for #229. 2015-07-24 15:32:47 +02:00
EinMByte
c612d21639 Fix #229 by returning zero instead of -1. 2015-07-24 15:13:09 +02:00
EinMByte
2bde6fc13b Fixes to GetMTUWindows and GetMTUWindowsIpv6 (thanks mlt). 2015-07-24 14:43:51 +02:00
EinMByte
c3ec75756d Also check the returned size in base64 tests. 2015-07-23 21:54:13 +02:00
EinMByte
469981cce5 Tests and documentation for base32. 2015-07-23 14:46:35 +02:00
EinMByte
28a4f4173d Tests and documentation for base64. 2015-07-22 16:43:11 +02:00
Meeh
53053105c9 Merge pull request #169 from guanqun/fix-binary-name
the binary should be i2pd
2015-07-22 15:03:51 +02:00
Riccardo Spagni
85b1505e51 Merge pull request #228 from EinMByte/master
Various changes
2015-07-22 14:34:29 +02:00
EinMByte
d0b2a0da14 Fix #227: Transports::GetRandomPeer() crashes if m_Peers is empty 2015-07-22 12:11:21 +02:00
EinMByte
28969bfeab Update todo. 2015-07-21 12:10:15 +02:00
EinMByte
6f569f5334 Merge remote-tracking branch 'github/master'
Conflicts:
	Log.cpp
	Log.h
2015-07-21 11:31:36 +02:00
Mikal Villa
5f644b1b04 Fixed build issue with Mac OSX, which don't support std::chrono::monotonic_clock. 2015-07-20 18:08:55 +02:00
EinMByte
eb4109187a Remove unused include (boost foreach). 2015-07-19 18:11:38 +02:00
EinMByte
79e3f669f0 Cleanup util, add documentation. 2015-07-19 17:52:28 +02:00
EinMByte
66754aac5a Tests and documentation for util::url. 2015-07-19 15:51:02 +02:00
EinMByte
4412dd198d Tabs -> spaces (yes this breaks compatiblity with upstream) 2015-07-16 23:29:52 +02:00
EinMByte
5d78e2f5e4 Setup unit test environment. 2015-07-16 22:28:57 +02:00
EinMByte
4ce7b0e9ef Minor changes based on cppcheck output. 2015-07-16 18:02:19 +02:00
EinMByte
62ac53563c Ensure zero-inialization, add TODO update gitignore. 2015-07-16 17:39:24 +02:00
orignal
c939dec26a check if current remote lease is not set 2015-07-10 09:25:12 -04:00
orignal
6e54714c0e use shared_ptr for garlic cloves 2015-07-07 11:08:36 -04:00
orignal
6851644922 shared_ptr for DatabaseStore, DatabaseLookup and DatabaseSearchReply 2015-07-07 10:55:58 -04:00
orignal
8f40167fe1 rollback of previous change. port must be accesses from a remote host 2015-07-06 18:57:56 -04:00
orignal
de6e73c2eb Merge pull request #224 from majestrate/master
bind I2PService and HTTPServer to loopback
2015-07-06 18:40:58 -04:00
jeff
855f3833d2 bind to loopback 2015-07-06 18:03:16 -04:00
jeff
37a5c10c66 Merge branch 'upstream-master' into http-bind-local 2015-07-06 17:45:17 -04:00
jeff
23275a5949 have http server bind to loopback 2015-07-06 17:45:12 -04:00
guanqun
d2b30b5d11 the binary should be i2pd 2015-03-27 11:19:04 +08:00
459 changed files with 33795 additions and 40302 deletions

31
.gitignore vendored
View File

@@ -1,13 +1,25 @@
# i2pd
obj/*.o
router.info
router.keys
i2p
libi2pd.so
netDb
/i2pd
/libi2pd.a
/libi2pdclient.a
tunnels.cfg
tests/tests
# Build files
CMakeCache.txt
build/CMakeFiles/*
build/tests
build/client
build/core
build/benchmark
build/i2pd
build/i2pd-tests
build/i2pd-benchmark
*.cmake
*.a
*.o
# Autotools
@@ -51,6 +63,14 @@ local.properties
# PDT-specific
.buildpath
#################
## Netbeans
#################
# CMake: if "Project with Existing Sources" is properly setup, then only the following needs to be ignored:
# (e.g., don't select "Automatic" but instead select "Run Configure Script in Subfolder:" into ./build/)
build/compile_commands.json
nbproject/
#################
## Visual Studio
@@ -234,6 +254,3 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
# Sphinx
docs/_build

View File

@@ -1,36 +0,0 @@
language: cpp
cache:
apt: true
os:
- linux
- osx
sudo: required
dist: trusty
addons:
apt:
packages:
- build-essential
- cmake
- g++
- clang
- libboost-chrono-dev
- libboost-date-time-dev
- libboost-filesystem-dev
- libboost-program-options-dev
- libboost-system-dev
- libboost-thread-dev
- libminiupnpc-dev
- libssl-dev
compiler:
- gcc
- clang
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install openssl miniupnpc ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink boost openssl && brew link boost openssl -f ; fi
env:
matrix:
- BUILD_TYPE=Release UPNP=ON
- BUILD_TYPE=Release UPNP=OFF
script:
- cd build && cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DWITH_UPNP=${UPNP} && make

View File

@@ -1,851 +0,0 @@
#include <string.h>
#include <inttypes.h>
#include <string>
#include <map>
#include <fstream>
#include <chrono>
#include <condition_variable>
#include <openssl/rand.h>
#include "Base.h"
#include "util.h"
#include "Identity.h"
#include "FS.h"
#include "Log.h"
#include "NetDb.h"
#include "ClientContext.h"
#include "AddressBook.h"
namespace i2p
{
namespace client
{
// TODO: this is actually proxy class
class AddressBookFilesystemStorage: public AddressBookStorage
{
private:
i2p::fs::HashedStorage storage;
std::string etagsPath, indexPath, localPath;
public:
AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {};
std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const;
void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
void RemoveAddress (const i2p::data::IdentHash& ident);
bool Init ();
int Load (std::map<std::string, i2p::data::IdentHash>& addresses);
int LoadLocal (std::map<std::string, i2p::data::IdentHash>& addresses);
int Save (const std::map<std::string, i2p::data::IdentHash>& addresses);
void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified);
bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified);
private:
int LoadFromFile (const std::string& filename, std::map<std::string, i2p::data::IdentHash>& addresses); // returns -1 if can't open file, otherwise number of records
};
bool AddressBookFilesystemStorage::Init()
{
storage.SetPlace(i2p::fs::GetDataDir());
// init storage
if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32))
{
// init ETags
etagsPath = i2p::fs::StorageRootPath (storage, "etags");
if (!i2p::fs::Exists (etagsPath))
i2p::fs::CreateDirectory (etagsPath);
// init address files
indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv");
localPath = i2p::fs::StorageRootPath (storage, "local.csv");
return true;
}
return false;
}
std::shared_ptr<const i2p::data::IdentityEx> AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const
{
std::string filename = storage.Path(ident.ToBase32());
std::ifstream f(filename, std::ifstream::binary);
if (!f.is_open ()) {
LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename);
return nullptr;
}
f.seekg (0,std::ios::end);
size_t len = f.tellg ();
if (len < i2p::data::DEFAULT_IDENTITY_SIZE) {
LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len);
return nullptr;
}
f.seekg(0, std::ios::beg);
uint8_t * buf = new uint8_t[len];
f.read((char *)buf, len);
auto address = std::make_shared<i2p::data::IdentityEx>(buf, len);
delete[] buf;
return address;
}
void AddressBookFilesystemStorage::AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
{
std::string path = storage.Path( address->GetIdentHash().ToBase32() );
std::ofstream f (path, std::ofstream::binary | std::ofstream::out);
if (!f.is_open ()) {
LogPrint (eLogError, "Addressbook: can't open file ", path);
return;
}
size_t len = address->GetFullLen ();
uint8_t * buf = new uint8_t[len];
address->ToBuffer (buf, len);
f.write ((char *)buf, len);
delete[] buf;
}
void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident)
{
storage.Remove( ident.ToBase32() );
}
int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map<std::string, i2p::data::IdentHash>& addresses)
{
int num = 0;
std::ifstream f (filename, std::ifstream::in); // in text mode
if (!f) return -1;
addresses.clear ();
while (!f.eof ())
{
std::string s;
getline(f, s);
if (!s.length()) continue; // skip empty line
std::size_t pos = s.find(',');
if (pos != std::string::npos)
{
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
i2p::data::IdentHash ident;
ident.FromBase32 (addr);
addresses[name] = ident;
num++;
}
}
return num;
}
int AddressBookFilesystemStorage::Load (std::map<std::string, i2p::data::IdentHash>& addresses)
{
int num = LoadFromFile (indexPath, addresses);
if (num < 0)
{
LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath);
return 0;
}
LogPrint(eLogInfo, "Addressbook: using index file ", indexPath);
LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage");
return num;
}
int AddressBookFilesystemStorage::LoadLocal (std::map<std::string, i2p::data::IdentHash>& addresses)
{
int num = LoadFromFile (localPath, addresses);
if (num < 0) return 0;
LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded");
return num;
}
int AddressBookFilesystemStorage::Save (const std::map<std::string, i2p::data::IdentHash>& addresses)
{
if (addresses.empty()) {
LogPrint(eLogWarning, "Addressbook: not saving empty addressbook");
return 0;
}
int num = 0;
std::ofstream f (indexPath, std::ofstream::out); // in text mode
if (!f.is_open ()) {
LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath);
return 0;
}
for (auto it: addresses) {
f << it.first << "," << it.second.ToBase32 () << std::endl;
num++;
}
LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved");
return num;
}
void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified)
{
std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt";
std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc);
if (f)
{
f << etag << std::endl;
f<< lastModified << std::endl;
}
}
bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified)
{
std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt";
std::ifstream f (fname, std::ofstream::in);
if (!f || f.eof ()) return false;
std::getline (f, etag);
if (f.eof ()) return false;
std::getline (f, lastModified);
return true;
}
//---------------------------------------------------------------------
AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false),
m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr)
{
}
AddressBook::~AddressBook ()
{
Stop ();
}
void AddressBook::Start ()
{
m_Storage->Init();
LoadHosts (); /* try storage, then hosts.txt, then download */
StartSubscriptions ();
StartLookups ();
}
void AddressBook::StartResolvers ()
{
LoadLocal ();
}
void AddressBook::Stop ()
{
StopLookups ();
StopSubscriptions ();
if (m_SubscriptionsUpdateTimer)
{
delete m_SubscriptionsUpdateTimer;
m_SubscriptionsUpdateTimer = nullptr;
}
if (m_IsDownloading)
{
LogPrint (eLogInfo, "Addressbook: subscriptions is downloading, abort");
for (int i = 0; i < 30; i++)
{
if (!m_IsDownloading)
{
LogPrint (eLogInfo, "Addressbook: subscriptions download complete");
break;
}
std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds
}
LogPrint (eLogError, "Addressbook: subscription download timeout");
m_IsDownloading = false;
}
if (m_Storage)
{
m_Storage->Save (m_Addresses);
delete m_Storage;
m_Storage = nullptr;
}
m_DefaultSubscription = nullptr;
for (auto it: m_Subscriptions)
delete it;
m_Subscriptions.clear ();
}
bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident)
{
auto pos = address.find(".b32.i2p");
if (pos != std::string::npos)
{
Base32ToByteStream (address.c_str(), pos, ident, 32);
return true;
}
else
{
pos = address.find (".i2p");
if (pos != std::string::npos)
{
auto identHash = FindAddress (address);
if (identHash)
{
ident = *identHash;
return true;
}
else
{
LookupAddress (address); // TODO:
return false;
}
}
}
// if not .b32 we assume full base64 address
i2p::data::IdentityEx dest;
if (!dest.FromBase64 (address))
return false;
ident = dest.GetIdentHash ();
return true;
}
const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address)
{
auto it = m_Addresses.find (address);
if (it != m_Addresses.end ())
return &it->second;
return nullptr;
}
void AddressBook::InsertAddress (const std::string& address, const std::string& base64)
{
auto ident = std::make_shared<i2p::data::IdentityEx>();
ident->FromBase64 (base64);
m_Storage->AddAddress (ident);
m_Addresses[address] = ident->GetIdentHash ();
LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ()));
}
void AddressBook::InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
{
m_Storage->AddAddress (address);
}
std::shared_ptr<const i2p::data::IdentityEx> AddressBook::GetAddress (const std::string& address)
{
i2p::data::IdentHash ident;
if (!GetIdentHash (address, ident)) return nullptr;
return m_Storage->GetAddress (ident);
}
void AddressBook::LoadHosts ()
{
if (m_Storage->Load (m_Addresses) > 0)
{
m_IsLoaded = true;
return;
}
// then try hosts.txt
std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode
if (f.is_open ())
{
LoadHostsFromStream (f);
m_IsLoaded = true;
}
}
bool AddressBook::LoadHostsFromStream (std::istream& f)
{
std::unique_lock<std::mutex> l(m_AddressBookMutex);
int numAddresses = 0;
bool incomplete = false;
std::string s;
while (!f.eof ())
{
getline(f, s);
if (!s.length())
continue; // skip empty line
size_t pos = s.find('=');
if (pos != std::string::npos)
{
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
auto ident = std::make_shared<i2p::data::IdentityEx> ();
if (ident->FromBase64(addr))
{
m_Addresses[name] = ident->GetIdentHash ();
m_Storage->AddAddress (ident);
numAddresses++;
}
else
{
LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name);
incomplete = f.eof ();
}
}
else
incomplete = f.eof ();
}
LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed");
if (numAddresses > 0)
{
if (!incomplete) m_IsLoaded = true;
m_Storage->Save (m_Addresses);
}
return !incomplete;
}
void AddressBook::LoadSubscriptions ()
{
if (!m_Subscriptions.size ())
{
std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode
if (f.is_open ())
{
std::string s;
while (!f.eof ())
{
getline(f, s);
if (!s.length()) continue; // skip empty line
m_Subscriptions.push_back (new AddressBookSubscription (*this, s));
}
LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded");
}
else
LogPrint (eLogWarning, "Addressbook: subscriptions.txt not found in datadir");
}
else
LogPrint (eLogError, "Addressbook: subscriptions already loaded");
}
void AddressBook::LoadLocal ()
{
std::map<std::string, i2p::data::IdentHash> localAddresses;
m_Storage->LoadLocal (localAddresses);
for (auto it: localAddresses)
{
auto dot = it.first.find ('.');
if (dot != std::string::npos)
{
auto domain = it.first.substr (dot + 1);
auto it1 = m_Addresses.find (domain); // find domain in our addressbook
if (it1 != m_Addresses.end ())
{
auto dest = context.FindLocalDestination (it1->second);
if (dest)
{
// address is ours
std::shared_ptr<AddressResolver> resolver;
auto it2 = m_Resolvers.find (it1->second);
if (it2 != m_Resolvers.end ())
resolver = it2->second; // resolver exists
else
{
// create new resolver
resolver = std::make_shared<AddressResolver>(dest);
m_Resolvers.insert (std::make_pair(it1->second, resolver));
}
resolver->AddAddress (it.first, it.second);
}
}
}
}
}
bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified)
{
if (m_Storage)
return m_Storage->GetEtag (subscription, etag, lastModified);
else
return false;
}
void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified)
{
m_IsDownloading = false;
int nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT;
if (success)
{
if (m_DefaultSubscription) m_DefaultSubscription.reset (nullptr);
if (m_IsLoaded)
nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT;
else
m_IsLoaded = true;
if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified);
}
if (m_SubscriptionsUpdateTimer)
{
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout));
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
this, std::placeholders::_1));
}
}
void AddressBook::StartSubscriptions ()
{
LoadSubscriptions ();
if (m_IsLoaded && m_Subscriptions.empty ()) return;
auto dest = i2p::client::context.GetSharedLocalDestination ();
if (dest)
{
m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ());
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT));
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
this, std::placeholders::_1));
}
else
LogPrint (eLogError, "Addressbook: can't start subscriptions: missing shared local destination");
}
void AddressBook::StopSubscriptions ()
{
if (m_SubscriptionsUpdateTimer)
m_SubscriptionsUpdateTimer->cancel ();
}
void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto dest = i2p::client::context.GetSharedLocalDestination ();
if (!dest) {
LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update");
return;
}
if (!m_IsDownloading && dest->IsReady ())
{
if (!m_IsLoaded)
{
// download it from http://i2p-projekt.i2p/hosts.txt
LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription.");
if (!m_DefaultSubscription)
m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS));
m_IsDownloading = true;
m_DefaultSubscription->CheckSubscription ();
}
else if (!m_Subscriptions.empty ())
{
// pick random subscription
auto ind = rand () % m_Subscriptions.size();
m_IsDownloading = true;
m_Subscriptions[ind]->CheckSubscription ();
}
}
else
{
// try it again later
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT));
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
this, std::placeholders::_1));
}
}
}
void AddressBook::StartLookups ()
{
auto dest = i2p::client::context.GetSharedLocalDestination ();
if (dest)
{
auto datagram = dest->GetDatagramDestination ();
if (!datagram)
datagram = dest->CreateDatagramDestination ();
datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
ADDRESS_RESPONSE_DATAGRAM_PORT);
}
}
void AddressBook::StopLookups ()
{
auto dest = i2p::client::context.GetSharedLocalDestination ();
if (dest)
{
auto datagram = dest->GetDatagramDestination ();
if (datagram)
datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT);
}
}
void AddressBook::LookupAddress (const std::string& address)
{
const i2p::data::IdentHash * ident = nullptr;
auto dot = address.find ('.');
if (dot != std::string::npos)
ident = FindAddress (address.substr (dot + 1));
if (!ident)
{
LogPrint (eLogError, "AddressBook: Can't find domain for ", address);
return;
}
auto dest = i2p::client::context.GetSharedLocalDestination ();
if (dest)
{
auto datagram = dest->GetDatagramDestination ();
if (datagram)
{
uint32_t nonce;
RAND_bytes ((uint8_t *)&nonce, 4);
{
std::unique_lock<std::mutex> l(m_LookupsMutex);
m_Lookups[nonce] = address;
}
LogPrint (eLogDebug, "AddressBook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce);
size_t len = address.length () + 9;
uint8_t * buf = new uint8_t[len];
memset (buf, 0, 4);
htobe32buf (buf + 4, nonce);
buf[8] = address.length ();
memcpy (buf + 9, address.c_str (), address.length ());
datagram->SendDatagramTo (buf, len, *ident, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT);
delete[] buf;
}
}
}
void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (len < 44)
{
LogPrint (eLogError, "AddressBook: Lookup response is too short ", len);
return;
}
uint32_t nonce = bufbe32toh (buf + 4);
LogPrint (eLogDebug, "AddressBook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce);
std::string address;
{
std::unique_lock<std::mutex> l(m_LookupsMutex);
auto it = m_Lookups.find (nonce);
if (it != m_Lookups.end ())
{
address = it->second;
m_Lookups.erase (it);
}
}
if (address.length () > 0)
{
// TODO: verify from
m_Addresses[address] = buf + 8;
}
}
AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link):
m_Book (book), m_Link (link)
{
}
void AddressBookSubscription::CheckSubscription ()
{
std::thread load_hosts(&AddressBookSubscription::Request, this);
load_hosts.detach(); // TODO: use join
}
void AddressBookSubscription::Request ()
{
// must be run in separate thread
LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
bool success = false;
i2p::util::http::url u (m_Link);
i2p::data::IdentHash ident;
if (m_Book.GetIdentHash (u.host_, ident))
{
if (!m_Etag.length ())
{
// load ETag
m_Book.GetEtag (ident, m_Etag, m_LastModified);
LogPrint (eLogInfo, "Addressbook: set ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
}
std::condition_variable newDataReceived;
std::mutex newDataReceivedMutex;
auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident);
if (!leaseSet)
{
std::unique_lock<std::mutex> l(newDataReceivedMutex);
i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident,
[&newDataReceived, &leaseSet](std::shared_ptr<i2p::data::LeaseSet> ls)
{
leaseSet = ls;
newDataReceived.notify_all ();
});
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
{
LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired");
i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident);
}
}
if (leaseSet)
{
std::stringstream request, response;
// standard header
request << "GET " << u.path_ << " HTTP/1.1\r\n"
<< "Host: " << u.host_ << "\r\n"
<< "Accept: */*\r\n"
<< "User-Agent: Wget/1.11.4\r\n"
//<< "Accept-Encoding: gzip\r\n"
<< "X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n"
<< "Connection: close\r\n";
if (m_Etag.length () > 0) // etag
request << i2p::util::http::IF_NONE_MATCH << ": " << m_Etag << "\r\n";
if (m_LastModified.length () > 0) // if-modfief-since
request << i2p::util::http::IF_MODIFIED_SINCE << ": " << m_LastModified << "\r\n";
request << "\r\n"; // end of header
auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_);
stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ());
uint8_t buf[4096];
bool end = false;
while (!end)
{
stream->AsyncReceive (boost::asio::buffer (buf, 4096),
[&](const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (bytes_transferred)
response.write ((char *)buf, bytes_transferred);
if (ecode == boost::asio::error::timed_out || !stream->IsOpen ())
end = true;
newDataReceived.notify_all ();
},
30); // wait for 30 seconds
std::unique_lock<std::mutex> l(newDataReceivedMutex);
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
LogPrint (eLogError, "Addressbook: subscriptions request timeout expired");
}
// process remaining buffer
while (size_t len = stream->ReadSome (buf, 4096))
response.write ((char *)buf, len);
// parse response
std::string version;
response >> version; // HTTP version
int status = 0;
response >> status; // status
if (status == 200) // OK
{
bool isChunked = false, isGzip = false;
m_Etag = ""; m_LastModified = "";
std::string header, statusMessage;
std::getline (response, statusMessage);
// read until new line meaning end of header
while (!response.eof () && header != "\r")
{
std::getline (response, header);
if (response.fail ()) break;
auto colon = header.find (':');
if (colon != std::string::npos)
{
std::string field = header.substr (0, colon);
boost::to_lower (field); // field are not case-sensitive
colon++;
header.resize (header.length () - 1); // delete \r
if (field == i2p::util::http::ETAG)
m_Etag = header.substr (colon + 1);
else if (field == i2p::util::http::LAST_MODIFIED)
m_LastModified = header.substr (colon + 1);
else if (field == i2p::util::http::TRANSFER_ENCODING)
isChunked = !header.compare (colon + 1, std::string::npos, "chunked");
else if (field == i2p::util::http::CONTENT_ENCODING)
isGzip = !header.compare (colon + 1, std::string::npos, "gzip") ||
!header.compare (colon + 1, std::string::npos, "x-i2p-gzip");
}
}
LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
if (!response.eof () && !response.fail ())
{
if (!isChunked)
success = ProcessResponse (response, isGzip);
else
{
// merge chunks
std::stringstream merged;
i2p::util::http::MergeChunkedResponse (response, merged);
success = ProcessResponse (merged, isGzip);
}
}
}
else if (status == 304)
{
success = true;
LogPrint (eLogInfo, "Addressbook: no updates from ", m_Link);
}
else
LogPrint (eLogWarning, "Adressbook: HTTP response ", status);
}
else
LogPrint (eLogError, "Addressbook: address ", u.host_, " not found");
}
else
LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_);
if (!success)
LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed");
m_Book.DownloadComplete (success, ident, m_Etag, m_LastModified);
}
bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip)
{
if (isGzip)
{
std::stringstream uncompressed;
i2p::data::GzipInflator inflator;
inflator.Inflate (s, uncompressed);
if (!uncompressed.fail ())
return m_Book.LoadHostsFromStream (uncompressed);
else
return false;
}
else
return m_Book.LoadHostsFromStream (s);
}
AddressResolver::AddressResolver (std::shared_ptr<ClientDestination> destination):
m_LocalDestination (destination)
{
if (m_LocalDestination)
{
auto datagram = m_LocalDestination->GetDatagramDestination ();
if (!datagram)
datagram = m_LocalDestination->CreateDatagramDestination ();
datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
ADDRESS_RESOLVER_DATAGRAM_PORT);
}
}
AddressResolver::~AddressResolver ()
{
if (m_LocalDestination)
{
auto datagram = m_LocalDestination->GetDatagramDestination ();
if (datagram)
datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT);
}
}
void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (len < 9 || len < buf[8] + 9U)
{
LogPrint (eLogError, "AddressBook: Address request is too short ", len);
return;
}
// read requested address
uint8_t l = buf[8];
char address[255];
memcpy (address, buf + 9, l);
address[l] = 0;
LogPrint (eLogDebug, "AddressBook: Address request ", address);
// send response
uint8_t response[44];
memset (response, 0, 4); // reserved
memcpy (response + 4, buf + 4, 4); // nonce
auto it = m_LocalAddresses.find (address); // address lookup
if (it != m_LocalAddresses.end ())
memcpy (response + 8, it->second, 32); // ident
else
memset (response + 8, 0, 32); // not found
memset (response + 40, 0, 4); // set expiration time to zero
m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash (), toPort, fromPort);
}
void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident)
{
m_LocalAddresses[name] = ident;
}
}
}

View File

@@ -1,147 +0,0 @@
#ifndef ADDRESS_BOOK_H__
#define ADDRESS_BOOK_H__
#include <string.h>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <mutex>
#include <memory>
#include <boost/asio.hpp>
#include "Base.h"
#include "Identity.h"
#include "Log.h"
#include "Destination.h"
namespace i2p
{
namespace client
{
const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes
const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes
const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours)
const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes
const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second
const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53;
const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54;
inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); }
class AddressBookStorage // interface for storage
{
public:
virtual ~AddressBookStorage () {};
virtual std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const = 0;
virtual void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) = 0;
virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0;
virtual bool Init () = 0;
virtual int Load (std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
virtual int LoadLocal (std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
virtual int Save (const std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0;
virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0;
};
class AddressBookSubscription;
class AddressResolver;
class AddressBook
{
public:
AddressBook ();
~AddressBook ();
void Start ();
void StartResolvers ();
void Stop ();
bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident);
std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const std::string& address);
const i2p::data::IdentHash * FindAddress (const std::string& address);
void LookupAddress (const std::string& address);
void InsertAddress (const std::string& address, const std::string& base64); // for jump service
void InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
bool LoadHostsFromStream (std::istream& f);
void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified);
//This method returns the ".b32.i2p" address
std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); }
std::string ToAddress(std::shared_ptr<const i2p::data::IdentityEx> ident) { return ToAddress(ident->GetIdentHash ()); }
bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified);
private:
void StartSubscriptions ();
void StopSubscriptions ();
void LoadHosts ();
void LoadSubscriptions ();
void LoadLocal ();
void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode);
void StartLookups ();
void StopLookups ();
void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
private:
std::mutex m_AddressBookMutex;
std::map<std::string, i2p::data::IdentHash> m_Addresses;
std::map<i2p::data::IdentHash, std::shared_ptr<AddressResolver> > m_Resolvers; // local destination->resolver
std::mutex m_LookupsMutex;
std::map<uint32_t, std::string> m_Lookups; // nonce -> address
AddressBookStorage * m_Storage;
volatile bool m_IsLoaded, m_IsDownloading;
std::vector<AddressBookSubscription *> m_Subscriptions;
std::unique_ptr<AddressBookSubscription> m_DefaultSubscription; // in case if we don't know any addresses yet
boost::asio::deadline_timer * m_SubscriptionsUpdateTimer;
};
class AddressBookSubscription
{
public:
AddressBookSubscription (AddressBook& book, const std::string& link);
void CheckSubscription ();
private:
void Request ();
bool ProcessResponse (std::stringstream& s, bool isGzip = false);
private:
AddressBook& m_Book;
std::string m_Link, m_Etag, m_LastModified;
// m_Etag must be surrounded by ""
};
class AddressResolver
{
public:
AddressResolver (std::shared_ptr<ClientDestination> destination);
~AddressResolver ();
void AddAddress (const std::string& name, const i2p::data::IdentHash& ident);
private:
void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
private:
std::shared_ptr<ClientDestination> m_LocalDestination;
std::map<std::string, i2p::data::IdentHash> m_LocalAddresses;
};
}
}
#endif

643
BOB.cpp
View File

@@ -1,643 +0,0 @@
#include <string.h>
#include <boost/lexical_cast.hpp>
#include "Log.h"
#include "ClientContext.h"
#include "BOB.h"
namespace i2p
{
namespace client
{
BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination):
BOBI2PTunnel (localDestination),
m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port))
{
}
BOBI2PInboundTunnel::~BOBI2PInboundTunnel ()
{
Stop ();
}
void BOBI2PInboundTunnel::Start ()
{
m_Acceptor.listen ();
Accept ();
}
void BOBI2PInboundTunnel::Stop ()
{
m_Acceptor.close();
ClearHandlers ();
}
void BOBI2PInboundTunnel::Accept ()
{
auto receiver = std::make_shared<AddressReceiver> ();
receiver->socket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this,
std::placeholders::_1, receiver));
}
void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver)
{
if (!ecode)
{
Accept ();
ReceiveAddress (receiver);
}
}
void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver)
{
receiver->socket->async_read_some (boost::asio::buffer(
receiver->buffer + receiver->bufferOffset,
BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset),
std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this,
std::placeholders::_1, std::placeholders::_2, receiver));
}
void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<AddressReceiver> receiver)
{
if (ecode)
LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ());
else
{
receiver->bufferOffset += bytes_transferred;
receiver->buffer[receiver->bufferOffset] = 0;
char * eol = strchr (receiver->buffer, '\n');
if (eol)
{
*eol = 0;
receiver->data = (uint8_t *)eol + 1;
receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1);
i2p::data::IdentHash ident;
if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident))
{
LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found");
return;
}
auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident);
if (leaseSet)
CreateConnection (receiver, leaseSet);
else
GetLocalDestination ()->RequestDestination (ident,
std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete,
this, std::placeholders::_1, receiver));
}
else
{
if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE)
ReceiveAddress (receiver);
else
LogPrint (eLogError, "BOB: missing inbound address");
}
}
}
void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver)
{
if (leaseSet)
CreateConnection (receiver, leaseSet);
else
LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found");
}
void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
{
LogPrint (eLogDebug, "BOB: New inbound connection");
auto connection = std::make_shared<I2PTunnelConnection>(this, receiver->socket, leaseSet);
AddHandler (connection);
connection->I2PConnect (receiver->data, receiver->dataLen);
}
BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet)
{
}
void BOBI2POutboundTunnel::Start ()
{
Accept ();
}
void BOBI2POutboundTunnel::Stop ()
{
ClearHandlers ();
}
void BOBI2POutboundTunnel::Accept ()
{
auto localDestination = GetLocalDestination ();
if (localDestination)
localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1));
else
LogPrint (eLogError, "BOB: Local destination not set for server tunnel");
}
void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), m_Endpoint, m_IsQuiet);
AddHandler (conn);
conn->Connect ();
}
}
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination):
m_LocalDestination (localDestination),
m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr)
{
}
BOBDestination::~BOBDestination ()
{
delete m_OutboundTunnel;
delete m_InboundTunnel;
i2p::client::context.DeleteLocalDestination (m_LocalDestination);
}
void BOBDestination::Start ()
{
if (m_OutboundTunnel) m_OutboundTunnel->Start ();
if (m_InboundTunnel) m_InboundTunnel->Start ();
}
void BOBDestination::Stop ()
{
StopTunnels ();
m_LocalDestination->Stop ();
}
void BOBDestination::StopTunnels ()
{
if (m_OutboundTunnel)
{
m_OutboundTunnel->Stop ();
delete m_OutboundTunnel;
m_OutboundTunnel = nullptr;
}
if (m_InboundTunnel)
{
m_InboundTunnel->Stop ();
delete m_InboundTunnel;
m_InboundTunnel = nullptr;
}
}
void BOBDestination::CreateInboundTunnel (int port)
{
if (!m_InboundTunnel)
m_InboundTunnel = new BOBI2PInboundTunnel (port, m_LocalDestination);
}
void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet)
{
if (!m_OutboundTunnel)
m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet);
}
BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner):
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0),
m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0),
m_CurrentDestination (nullptr)
{
}
BOBCommandSession::~BOBCommandSession ()
{
}
void BOBCommandSession::Terminate ()
{
m_Socket.close ();
m_IsOpen = false;
}
void BOBCommandSession::Receive ()
{
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset),
std::bind(&BOBCommandSession::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
size_t size = m_ReceiveBufferOffset + bytes_transferred;
m_ReceiveBuffer[size] = 0;
char * eol = strchr (m_ReceiveBuffer, '\n');
if (eol)
{
*eol = 0;
char * operand = strchr (m_ReceiveBuffer, ' ');
if (operand)
{
*operand = 0;
operand++;
}
else
operand = eol;
// process command
auto& handlers = m_Owner.GetCommandHandlers ();
auto it = handlers.find (m_ReceiveBuffer);
if (it != handlers.end ())
(this->*(it->second))(operand, eol - operand);
else
{
LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer);
SendReplyError ("unknown command");
}
m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1;
memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset);
}
else
{
if (size < BOB_COMMAND_BUFFER_SIZE)
m_ReceiveBufferOffset = size;
else
{
LogPrint (eLogError, "BOB: Malformed input of the command channel");
Terminate ();
}
}
}
}
void BOBCommandSession::Send (size_t len)
{
boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len),
boost::asio::transfer_all (),
std::bind(&BOBCommandSession::HandleSent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (m_IsOpen)
Receive ();
else
Terminate ();
}
}
void BOBCommandSession::SendReplyOK (const char * msg)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
#endif
Send (len);
}
void BOBCommandSession::SendReplyError (const char * msg)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
#endif
Send (len);
}
void BOBCommandSession::SendVersion ()
{
size_t len = strlen (BOB_VERSION);
memcpy (m_SendBuffer, BOB_VERSION, len);
Send (len);
}
void BOBCommandSession::SendData (const char * nickname)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
#endif
Send (len);
}
void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: zap");
Terminate ();
}
void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: quit");
m_IsOpen = false;
SendReplyOK ("Bye!");
}
void BOBCommandSession::StartCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: start ", m_Nickname);
if (!m_CurrentDestination)
{
m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options));
m_Owner.AddDestination (m_Nickname, m_CurrentDestination);
}
if (m_InPort)
m_CurrentDestination->CreateInboundTunnel (m_InPort);
if (m_OutPort && !m_Address.empty ())
m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet);
m_CurrentDestination->Start ();
SendReplyOK ("tunnel starting");
}
void BOBCommandSession::StopCommandHandler (const char * operand, size_t len)
{
auto dest = m_Owner.FindDestination (m_Nickname);
if (dest)
{
dest->StopTunnels ();
SendReplyOK ("tunnel stopping");
}
else
SendReplyError ("tunnel not found");
}
void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: setnick ", operand);
m_Nickname = operand;
std::string msg ("Nickname set to ");
msg += operand;
SendReplyOK (msg.c_str ());
}
void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: getnick ", operand);
m_CurrentDestination = m_Owner.FindDestination (operand);
if (m_CurrentDestination)
{
m_Keys = m_CurrentDestination->GetKeys ();
m_Nickname = operand;
std::string msg ("Nickname set to ");
msg += operand;
SendReplyOK (msg.c_str ());
}
else
SendReplyError ("tunnel not found");
}
void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: newkeys");
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys ();
SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ());
}
void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: setkeys ", operand);
m_Keys.FromBase64 (operand);
SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ());
}
void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: getkeys");
SendReplyOK (m_Keys.ToBase64 ().c_str ());
}
void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: getdest");
SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ());
}
void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: outhost ", operand);
m_Address = operand;
SendReplyOK ("outhost set");
}
void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: outport ", operand);
m_OutPort = boost::lexical_cast<int>(operand);
SendReplyOK ("outbound port set");
}
void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: inhost ", operand);
m_Address = operand;
SendReplyOK ("inhost set");
}
void BOBCommandSession::InportCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: inport ", operand);
m_InPort = boost::lexical_cast<int>(operand);
SendReplyOK ("inbound port set");
}
void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: quiet");
m_IsQuiet = true;
SendReplyOK ("quiet");
}
void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: lookup ", operand);
i2p::data::IdentHash ident;
if (!context.GetAddressBook ().GetIdentHash (operand, ident) || !m_CurrentDestination)
{
SendReplyError ("Address Not found");
return;
}
auto localDestination = m_CurrentDestination->GetLocalDestination ();
auto leaseSet = localDestination->FindLeaseSet (ident);
if (leaseSet)
SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ());
else
{
auto s = shared_from_this ();
localDestination->RequestDestination (ident,
[s](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls)
s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ());
else
s->SendReplyError ("LeaseSet Not found");
}
);
}
}
void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: clear");
m_Owner.DeleteDestination (m_Nickname);
SendReplyOK ("cleared");
}
void BOBCommandSession::ListCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: list");
auto& destinations = m_Owner.GetDestinations ();
for (auto it: destinations)
SendData (it.first.c_str ());
SendReplyOK ("Listing done");
}
void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: option ", operand);
const char * value = strchr (operand, '=');
if (value)
{
*(const_cast<char *>(value)) = 0;
m_Options[operand] = value + 1;
*(const_cast<char *>(value)) = '=';
SendReplyOK ("option");
}
else
SendReplyError ("malformed");
}
BOBCommandChannel::BOBCommandChannel (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port))
{
// command -> handler
m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler;
m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler;
m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler;
m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler;
m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler;
m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler;
m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler;
m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler;
m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler;
m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler;
m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler;
m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler;
m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler;
m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler;
m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler;
m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler;
m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler;
m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
}
BOBCommandChannel::~BOBCommandChannel ()
{
Stop ();
for (auto it: m_Destinations)
delete it.second;
}
void BOBCommandChannel::Start ()
{
Accept ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this));
}
void BOBCommandChannel::Stop ()
{
m_IsRunning = false;
for (auto it: m_Destinations)
it.second->Stop ();
m_Acceptor.cancel ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void BOBCommandChannel::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "BOB: runtime exception: ", ex.what ());
}
}
}
void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest)
{
m_Destinations[name] = dest;
}
void BOBCommandChannel::DeleteDestination (const std::string& name)
{
auto it = m_Destinations.find (name);
if (it != m_Destinations.end ())
{
it->second->Stop ();
delete it->second;
m_Destinations.erase (it);
}
}
BOBDestination * BOBCommandChannel::FindDestination (const std::string& name)
{
auto it = m_Destinations.find (name);
if (it != m_Destinations.end ())
return it->second;
return nullptr;
}
void BOBCommandChannel::Accept ()
{
auto newSession = std::make_shared<BOBCommandSession> (*this);
m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this,
std::placeholders::_1, newSession));
}
void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session)
{
if (ecode != boost::asio::error::operation_aborted)
Accept ();
if (!ecode)
{
LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ());
session->SendVersion ();
}
else
LogPrint (eLogError, "BOB: accept error: ", ecode.message ());
}
}
}

237
BOB.h
View File

@@ -1,237 +0,0 @@
#ifndef BOB_H__
#define BOB_H__
#include <inttypes.h>
#include <thread>
#include <memory>
#include <map>
#include <string>
#include <boost/asio.hpp>
#include "I2PTunnel.h"
#include "I2PService.h"
#include "Identity.h"
#include "LeaseSet.h"
namespace i2p
{
namespace client
{
const size_t BOB_COMMAND_BUFFER_SIZE = 1024;
const char BOB_COMMAND_ZAP[] = "zap";
const char BOB_COMMAND_QUIT[] = "quit";
const char BOB_COMMAND_START[] = "start";
const char BOB_COMMAND_STOP[] = "stop";
const char BOB_COMMAND_SETNICK[] = "setnick";
const char BOB_COMMAND_GETNICK[] = "getnick";
const char BOB_COMMAND_NEWKEYS[] = "newkeys";
const char BOB_COMMAND_GETKEYS[] = "getkeys";
const char BOB_COMMAND_SETKEYS[] = "setkeys";
const char BOB_COMMAND_GETDEST[] = "getdest";
const char BOB_COMMAND_OUTHOST[] = "outhost";
const char BOB_COMMAND_OUTPORT[] = "outport";
const char BOB_COMMAND_INHOST[] = "inhost";
const char BOB_COMMAND_INPORT[] = "inport";
const char BOB_COMMAND_QUIET[] = "quiet";
const char BOB_COMMAND_LOOKUP[] = "lookup";
const char BOB_COMMAND_CLEAR[] = "clear";
const char BOB_COMMAND_LIST[] = "list";
const char BOB_COMMAND_OPTION[] = "option";
const char BOB_VERSION[] = "BOB 00.00.10\nOK\n";
const char BOB_REPLY_OK[] = "OK %s\n";
const char BOB_REPLY_ERROR[] = "ERROR %s\n";
const char BOB_DATA[] = "NICKNAME %s\n";
class BOBI2PTunnel: public I2PService
{
public:
BOBI2PTunnel (std::shared_ptr<ClientDestination> localDestination):
I2PService (localDestination) {};
virtual void Start () {};
virtual void Stop () {};
};
class BOBI2PInboundTunnel: public BOBI2PTunnel
{
struct AddressReceiver
{
std::shared_ptr<boost::asio::ip::tcp::socket> socket;
char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address
uint8_t * data; // pointer to buffer
size_t dataLen, bufferOffset;
AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {};
};
public:
BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination);
~BOBI2PInboundTunnel ();
void Start ();
void Stop ();
private:
void Accept ();
void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver);
void ReceiveAddress (std::shared_ptr<AddressReceiver> receiver);
void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<AddressReceiver> receiver);
void HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver);
void CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet);
private:
boost::asio::ip::tcp::acceptor m_Acceptor;
};
class BOBI2POutboundTunnel: public BOBI2PTunnel
{
public:
BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
void Start ();
void Stop ();
void SetQuiet () { m_IsQuiet = true; };
private:
void Accept ();
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
private:
boost::asio::ip::tcp::endpoint m_Endpoint;
bool m_IsQuiet;
};
class BOBDestination
{
public:
BOBDestination (std::shared_ptr<ClientDestination> localDestination);
~BOBDestination ();
void Start ();
void Stop ();
void StopTunnels ();
void CreateInboundTunnel (int port);
void CreateOutboundTunnel (const std::string& address, int port, bool quiet);
const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); };
std::shared_ptr<ClientDestination> GetLocalDestination () const { return m_LocalDestination; };
private:
std::shared_ptr<ClientDestination> m_LocalDestination;
BOBI2POutboundTunnel * m_OutboundTunnel;
BOBI2PInboundTunnel * m_InboundTunnel;
};
class BOBCommandChannel;
class BOBCommandSession: public std::enable_shared_from_this<BOBCommandSession>
{
public:
BOBCommandSession (BOBCommandChannel& owner);
~BOBCommandSession ();
void Terminate ();
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
void SendVersion ();
// command handlers
void ZapCommandHandler (const char * operand, size_t len);
void QuitCommandHandler (const char * operand, size_t len);
void StartCommandHandler (const char * operand, size_t len);
void StopCommandHandler (const char * operand, size_t len);
void SetNickCommandHandler (const char * operand, size_t len);
void GetNickCommandHandler (const char * operand, size_t len);
void NewkeysCommandHandler (const char * operand, size_t len);
void SetkeysCommandHandler (const char * operand, size_t len);
void GetkeysCommandHandler (const char * operand, size_t len);
void GetdestCommandHandler (const char * operand, size_t len);
void OuthostCommandHandler (const char * operand, size_t len);
void OutportCommandHandler (const char * operand, size_t len);
void InhostCommandHandler (const char * operand, size_t len);
void InportCommandHandler (const char * operand, size_t len);
void QuietCommandHandler (const char * operand, size_t len);
void LookupCommandHandler (const char * operand, size_t len);
void ClearCommandHandler (const char * operand, size_t len);
void ListCommandHandler (const char * operand, size_t len);
void OptionCommandHandler (const char * operand, size_t len);
private:
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void Send (size_t len);
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void SendReplyOK (const char * msg);
void SendReplyError (const char * msg);
void SendData (const char * nickname);
private:
BOBCommandChannel& m_Owner;
boost::asio::ip::tcp::socket m_Socket;
char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1];
size_t m_ReceiveBufferOffset;
bool m_IsOpen, m_IsQuiet;
std::string m_Nickname, m_Address;
int m_InPort, m_OutPort;
i2p::data::PrivateKeys m_Keys;
std::map<std::string, std::string> m_Options;
BOBDestination * m_CurrentDestination;
};
typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len);
class BOBCommandChannel
{
public:
BOBCommandChannel (const std::string& address, int port);
~BOBCommandChannel ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
void AddDestination (const std::string& name, BOBDestination * dest);
void DeleteDestination (const std::string& name);
BOBDestination * FindDestination (const std::string& name);
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
std::map<std::string, BOBDestination *> m_Destinations;
std::map<std::string, BOBCommandHandler> m_CommandHandlers;
public:
const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; };
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
};
}
}
#endif

390
Base.cpp
View File

@@ -1,390 +0,0 @@
#include <stdlib.h>
#include "Log.h"
#include "Base.h"
namespace i2p
{
namespace data
{
static const char T32[32] = {
'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', '2', '3', '4', '5', '6', '7',
};
const char * GetBase32SubstitutionTable ()
{
return T32;
}
static void iT64Build(void);
/*
*
* BASE64 Substitution Table
* -------------------------
*
* Direct Substitution Table
*/
static const char T64[64] = {
'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', '-', '~'
};
const char * GetBase64SubstitutionTable ()
{
return T64;
}
/*
* Reverse Substitution Table (built in run time)
*/
static char iT64[256];
static int isFirstTime = 1;
/*
* Padding
*/
static char P64 = '=';
/*
*
* ByteStreamToBase64
* ------------------
*
* Converts binary encoded data to BASE64 format.
*
*/
size_t /* Number of bytes in the encoded buffer */
ByteStreamToBase64 (
const uint8_t * InBuffer, /* Input buffer, binary data */
size_t InCount, /* Number of bytes in the input buffer */
char * OutBuffer, /* output buffer */
size_t len /* length of output buffer */
)
{
unsigned char * ps;
unsigned char * pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
ps = (unsigned char *)InBuffer;
n = InCount/3;
m = InCount%3;
if (!m)
outCount = 4*n;
else
outCount = 4*(n+1);
if (outCount > len) return 0;
pd = (unsigned char *)OutBuffer;
for ( i = 0; i<n; i++ ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x30;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<=2;
acc_2 = *ps++;
acc_1 |= acc_2>>6; /* base64 digit #3 */
*pd++ = T64[acc_1];
acc_2 &= 0x3f; /* base64 digit #4 */
*pd++ = T64[acc_2];
}
if ( m == 1 ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
*pd++ = T64[acc_2];
*pd++ = P64;
*pd++ = P64;
}
else if ( m == 2 ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x3f;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<=2; /* base64 digit #3 */
*pd++ = T64[acc_1];
*pd++ = P64;
}
return outCount;
}
/*
*
* Base64ToByteStream
* ------------------
*
* Converts BASE64 encoded data to binary format. If input buffer is
* not properly padded, buffer of negative length is returned
*
*/
size_t /* Number of output bytes */
Base64ToByteStream (
const char * InBuffer, /* BASE64 encoded buffer */
size_t InCount, /* Number of input bytes */
uint8_t * OutBuffer, /* output buffer length */
size_t len /* length of output buffer */
)
{
unsigned char * ps;
unsigned char * pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
if (isFirstTime) iT64Build();
n = InCount/4;
m = InCount%4;
if (InCount && !m)
outCount = 3*n;
else {
outCount = 0;
return 0;
}
ps = (unsigned char *)(InBuffer + InCount - 1);
while ( *ps-- == P64 ) outCount--;
ps = (unsigned char *)InBuffer;
if (outCount > len) return -1;
pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount;
for ( i = 0; i < n; i++ ){
acc_1 = iT64[*ps++];
acc_2 = iT64[*ps++];
acc_1 <<= 2;
acc_1 |= acc_2>>4;
*pd++ = acc_1;
if (pd >= endOfOutBuffer) break;
acc_2 <<= 4;
acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2;
*pd++ = acc_2;
if (pd >= endOfOutBuffer) break;
acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6;
*pd++ = acc_2;
}
return outCount;
}
size_t Base64EncodingBufferSize (const size_t input_size)
{
auto d = div (input_size, 3);
if (d.rem) d.quot++;
return 4*d.quot;
}
/*
*
* iT64
* ----
* Reverse table builder. P64 character is replaced with 0
*
*
*/
static void iT64Build()
{
int i;
isFirstTime = 0;
for ( i=0; i<256; i++ ) iT64[i] = -1;
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
iT64[(int)P64] = 0;
}
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
{
int tmp = 0, bits = 0;
size_t ret = 0;
for (size_t i = 0; i < len; i++)
{
char ch = inBuf[i];
if (ch >= '2' && ch <= '7') // digit
ch = (ch - '2') + 26; // 26 means a-z
else if (ch >= 'a' && ch <= 'z')
ch = ch - 'a'; // a = 0
else
return 0; // unexpected character
tmp |= ch;
bits += 5;
if (bits >= 8)
{
if (ret >= outLen) return ret;
outBuf[ret] = tmp >> (bits - 8);
bits -= 8;
ret++;
}
tmp <<= 5;
}
return ret;
}
size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
{
size_t ret = 0, pos = 1;
int bits = 8, tmp = inBuf[0];
while (ret < outLen && (bits > 0 || pos < len))
{
if (bits < 5)
{
if (pos < len)
{
tmp <<= 8;
tmp |= inBuf[pos] & 0xFF;
pos++;
bits += 8;
}
else // last byte
{
tmp <<= (5 - bits);
bits = 5;
}
}
bits -= 5;
int ind = (tmp >> bits) & 0x1F;
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
ret++;
}
return ret;
}
GzipInflator::GzipInflator (): m_IsDirty (false)
{
memset (&m_Inflator, 0, sizeof (m_Inflator));
inflateInit2 (&m_Inflator, MAX_WBITS + 16); // gzip
}
GzipInflator::~GzipInflator ()
{
inflateEnd (&m_Inflator);
}
size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen)
{
if (m_IsDirty) inflateReset (&m_Inflator);
m_IsDirty = true;
m_Inflator.next_in = const_cast<uint8_t *>(in);
m_Inflator.avail_in = inLen;
m_Inflator.next_out = out;
m_Inflator.avail_out = outLen;
int err;
if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END)
return outLen - m_Inflator.avail_out;
else
{
LogPrint (eLogError, "Decompression error ", err);
return 0;
}
}
bool GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& s)
{
m_IsDirty = true;
uint8_t * out = new uint8_t[GZIP_CHUNK_SIZE];
m_Inflator.next_in = const_cast<uint8_t *>(in);
m_Inflator.avail_in = inLen;
int ret;
do
{
m_Inflator.next_out = out;
m_Inflator.avail_out = GZIP_CHUNK_SIZE;
ret = inflate (&m_Inflator, Z_NO_FLUSH);
if (ret < 0)
{
LogPrint (eLogError, "Decompression error ", ret);
inflateEnd (&m_Inflator);
s.setstate(std::ios_base::failbit);
break;
}
else
s.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out);
}
while (!m_Inflator.avail_out); // more data to read
delete[] out;
return ret == Z_STREAM_END || ret < 0;
}
void GzipInflator::Inflate (std::istream& in, std::ostream& out)
{
uint8_t * buf = new uint8_t[GZIP_CHUNK_SIZE];
while (!in.eof ())
{
in.read ((char *)buf, GZIP_CHUNK_SIZE);
Inflate (buf, in.gcount (), out);
}
delete[] buf;
}
GzipDeflator::GzipDeflator (): m_IsDirty (false)
{
memset (&m_Deflator, 0, sizeof (m_Deflator));
deflateInit2 (&m_Deflator, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); // 15 + 16 sets gzip
}
GzipDeflator::~GzipDeflator ()
{
deflateEnd (&m_Deflator);
}
void GzipDeflator::SetCompressionLevel (int level)
{
deflateParams (&m_Deflator, level, Z_DEFAULT_STRATEGY);
}
size_t GzipDeflator::Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen)
{
if (m_IsDirty) deflateReset (&m_Deflator);
m_IsDirty = true;
m_Deflator.next_in = const_cast<uint8_t *>(in);
m_Deflator.avail_in = inLen;
m_Deflator.next_out = out;
m_Deflator.avail_out = outLen;
int err;
if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END)
return outLen - m_Deflator.avail_out;
else
{
LogPrint (eLogError, "Compression error ", err);
return 0;
}
}
}
}

134
Base.h
View File

@@ -1,134 +0,0 @@
#ifndef BASE_H__
#define BASE_H__
#include <inttypes.h>
#include <string.h>
#include <string>
#include <zlib.h>
#include <iostream>
namespace i2p
{
namespace data
{
size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
const char * GetBase32SubstitutionTable ();
const char * GetBase64SubstitutionTable ();
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen);
/**
Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes
*/
size_t Base64EncodingBufferSize(const size_t input_size);
template<int sz>
class Tag
{
public:
Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); };
Tag (const Tag<sz>& ) = default;
#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it
Tag (Tag<sz>&& ) = default;
#endif
Tag () = default;
Tag<sz>& operator= (const Tag<sz>& ) = default;
#ifndef _WIN32
Tag<sz>& operator= (Tag<sz>&& ) = default;
#endif
uint8_t * operator()() { return m_Buf; };
const uint8_t * operator()() const { return m_Buf; };
operator uint8_t * () { return m_Buf; };
operator const uint8_t * () const { return m_Buf; };
const uint64_t * GetLL () const { return ll; };
bool operator== (const Tag<sz>& other) const { return !memcmp (m_Buf, other.m_Buf, sz); };
bool operator< (const Tag<sz>& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; };
bool IsZero () const
{
for (int i = 0; i < sz/8; i++)
if (ll[i]) return false;
return true;
}
std::string ToBase64 () const
{
char str[sz*2];
int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2);
str[l] = 0;
return std::string (str);
}
std::string ToBase32 () const
{
char str[sz*2];
int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2);
str[l] = 0;
return std::string (str);
}
void FromBase32 (const std::string& s)
{
i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
void FromBase64 (const std::string& s)
{
i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
private:
union // 8 bytes alignment
{
uint8_t m_Buf[sz];
uint64_t ll[sz/8];
};
};
const size_t GZIP_CHUNK_SIZE = 16384;
class GzipInflator
{
public:
GzipInflator ();
~GzipInflator ();
size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen);
bool Inflate (const uint8_t * in, size_t inLen, std::ostream& s);
// return true when finshed or error, s failbit will be set in case of error
void Inflate (std::istream& in, std::ostream& out);
private:
z_stream m_Inflator;
bool m_IsDirty;
};
class GzipDeflator
{
public:
GzipDeflator ();
~GzipDeflator ();
void SetCompressionLevel (int level);
size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen);
private:
z_stream m_Deflator;
bool m_IsDirty;
};
}
}
#endif

195
CMakeLists.txt Normal file
View File

@@ -0,0 +1,195 @@
cmake_minimum_required ( VERSION 2.8.12 )
project ( "i2pd" )
# configurale options
option(WITH_AESNI "Use AES-NI instructions set" OFF)
option(WITH_HARDENING "Use hardening compiler flags" OFF)
option(WITH_LIBRARY "Build library" ON)
option(WITH_BINARY "Build binary" ON)
option(WITH_STATIC "Static build" OFF)
option(WITH_UPNP "Include support for UPnP client" OFF)
option(WITH_TESTS "Build unit tests" OFF)
option(WITH_BENCHMARK "Build benchmarking code" OFF)
option(WITH_OPTIMIZE "Optimization flags" OFF)
option(I2PD_DATA_PATH "The path to the i2pd data folder")
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
# Default build is Debug
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# compiler flags customization (by vendor)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic")
# TODO: The following is incompatible with static build and enabled hardening
# for OpenWRT.
# Multiple definitions of __stack_chk_fail (libssp & libc)
set(
CMAKE_CXX_FLAGS_MINSIZEREL
"${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections"
)
# -flto is added from above
set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections")
endif()
# Check for c++11 support
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" CXX0X_SUPPORTED)
if(CXX11_SUPPORTED)
add_definitions("-std=c++11")
elseif(CXX0X_SUPPORTED) # gcc 4.6
add_definitions("-std=c++0x")
elseif(NOT MSVC)
message(SEND_ERROR "C++11 standard not supported by compiler. Version too old?")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(WITH_HARDENING)
add_definitions( "-D_FORTIFY_SOURCE=2" )
set(
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security"
)
set(
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fstack-protector-strong -fPIE --param ssp-buffer-size=4 -z relro -z now"
)
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# TODO: Clang-specific flags
endif()
# Compiler flags customization (by system)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
# "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8
add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" )
endif ()
if(WITH_UPNP)
add_definitions(-DUSE_UPNP)
if(NOT MSVC)
set(DL_LIB ${CMAKE_DL_LIBS})
endif()
endif()
if(WITH_AESNI)
add_definitions( "-maes -DAESNI")
endif()
if(WITH_OPTIMIZE AND (NOT MSVC))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
# Libraries
# TODO: once CMake 3.1+ becomes mainstream, see e.g.
# http://stackoverflow.com/a/29871891/673826 use imported Threads::Threads
# instead
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package (Threads REQUIRED)
if(THREADS_HAVE_PTHREAD_ARG) # compile time flag
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
if(WITH_STATIC)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME OFF)
if(NOT WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()
set(BUILD_SHARED_LIBS OFF)
if(${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(
CMAKE_THREAD_LIBS_INIT
"gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel"
)
endif()
elseif(NOT WIN32)
# TODO: Consider separate compilation for COMMON_SRC for library.
# No need in -fPIC overhead for binary if not interested in library
# HINT: revert c266cff CMakeLists.txt: compilation speed up
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
else() # Not a static build
add_definitions(-DBOOST_ALL_DYN_LINK)
endif()
find_package(
Boost COMPONENTS
system filesystem regex program_options date_time thread chrono REQUIRED
)
if(NOT DEFINED Boost_INCLUDE_DIRS)
message(SEND_ERROR "Boost not found, or version below 1.46. Please download Boost!")
endif()
find_package(CryptoPP REQUIRED)
if(NOT DEFINED CRYPTO++_INCLUDE_DIR)
message(SEND_ERROR "Could not find Crypto++. Please download and install it first!")
endif()
find_package(MiniUPnPc)
if(NOT ${MINIUPNPC_FOUND})
set(WITH_UPNP OFF)
endif()
# Load includes
include_directories(
${CMAKE_SOURCE_DIR} ${Boost_INCLUDE_DIRS} ${CRYPTO++_INCLUDE_DIR}
"core/"
)
if(I2PD_DATA_PATH)
set(I2PD_DATA_DIR ${I2PD_DATA_PATH})
# Using custom path, make sure the code knows about this
add_definitions(-DI2PD_CUSTOM_DATA_PATH="${I2PD_DATA_PATH}")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(I2PD_DATA_DIR "$ENV{APPDATA}\\i2pd")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(I2PD_DATA_DIR "$ENV{HOME}/Library/Application Support/i2pd")
else()
set(I2PD_DATA_DIR "$ENV{HOME}/.i2pd")
endif()
# Show summary
message(STATUS "---------------------------------------")
message(STATUS "Build type : ${CMAKE_BUILD_TYPE}")
message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}")
message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}")
message(STATUS "I2PD data directory: ${I2PD_DATA_DIR}")
message(STATUS "Options:")
message(STATUS " AESNI : ${WITH_AESNI}")
message(STATUS " HARDENING : ${WITH_HARDENING}")
message(STATUS " LIBRARY : ${WITH_LIBRARY}")
message(STATUS " BINARY : ${WITH_BINARY}")
message(STATUS " STATIC BUILD : ${WITH_STATIC}")
message(STATUS " UPnP : ${WITH_UPNP}")
message(STATUS " TESTS : ${WITH_TESTS}")
message(STATUS " BENCHMARKING : ${WITH_BENCHMARK}")
message(STATUS " OPTIMIZATION : ${WITH_OPTIMIZE}")
message(STATUS "---------------------------------------")
# Handle paths nicely
include(GNUInstallDirs)
set(CORE_NAME "${PROJECT_NAME}-core")
set(CLIENT_NAME "${PROJECT_NAME}-client")
set(TESTS_NAME "${PROJECT_NAME}-tests")
set(BENCHMARK_NAME "${PROJECT_NAME}-benchmark")
add_subdirectory(core)
add_subdirectory(client)
add_subdirectory(tests)
add_subdirectory(benchmark)
if(WITH_BINARY)
file(MAKE_DIRECTORY "${I2PD_DATA_DIR}/webui")
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/webui" DESTINATION "${I2PD_DATA_DIR}")
endif()

View File

@@ -1,86 +0,0 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
## [2.8.0] - UNRELEASED
### Changed
- Proxy refactoring & speedup
- I2PControl refactoring & fixes (proper jsonrpc responses on errors)
- boost::regex no more needed
### Fixed
- initscripts: added openrc one, in sysv-ish make I2PD_PORT optional
## [2.7.0] - 2016-05-18
### Added
- Precomputed El-Gamal/DH tables
- Configurable limit of transit tunnels
### Changed
- Speed-up of assymetric crypto for non-x64 platforms
- Refactoring of web-console
## [2.6.0] - 2016-03-31
### Added
- Gracefull shutdown on SIGINT
- Numeric bandwidth limits (was: by router class)
- Jumpservices in web-console
- Logging to syslog
- Tray icon for windows application
### Changed
- Logs refactoring
- Improved statistics in web-console
### Deprecated:
- Renamed main/tunnels config files (will use old, if found, but emits warning)
## [2.5.1] - 2016-03-10
### Fixed
- Doesn't create ~/.i2pd dir if missing
## [2.5.0] - 2016-03-04
### Added
- IRC server tunnels
- SOCKS outproxy support
- Support for gzipped addressbook updates
- Support for router families
### Changed
- Shared RTT/RTO between streams
- Filesystem work refactoring
## [2.4.0] - 2016-02-03
### Added
- X-I2P-* headers for server http-tunnels
- I2CP options for I2P tunnels
- Show I2P tunnels in webconsole
### Changed
- Refactoring of cmdline/config parsing
## [2.3.0] - 2016-01-12
### Added
- Support for new router bandwidth class codes (P and X)
- I2PControl supports external webui
- Added --pidfile and --notransit parameters
- Ability to specify signature type for i2p tunnel
### Changed
- Fixed multiple floodfill-related bugs
- New webconsole layout
## [2.2.0] - 2015-12-22
### Added
- Ability to connect to router without ip via introducer
### Changed
- Persist temporary encryption keys for local destinations
- Performance improvements for EdDSA
- New addressbook structure
## [2.1.0] - 2015-11-12
### Added
- Implementation of EdDSA
### Changed
- EdDSA is default signature type for new RouterInfos

View File

@@ -1,444 +0,0 @@
#include <fstream>
#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "Config.h"
#include "FS.h"
#include "Log.h"
#include "Identity.h"
#include "util.h"
#include "ClientContext.h"
namespace i2p
{
namespace client
{
ClientContext context;
ClientContext::ClientContext (): m_SharedLocalDestination (nullptr),
m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr),
m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr)
{
}
ClientContext::~ClientContext ()
{
delete m_HttpProxy;
delete m_SocksProxy;
delete m_SamBridge;
delete m_BOBCommandChannel;
delete m_I2CPServer;
}
void ClientContext::Start ()
{
if (!m_SharedLocalDestination)
{
m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, DSA
m_Destinations[m_SharedLocalDestination->GetIdentity ()->GetIdentHash ()] = m_SharedLocalDestination;
m_SharedLocalDestination->Start ();
}
m_AddressBook.Start ();
std::shared_ptr<ClientDestination> localDestination;
bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy);
if (httproxy) {
std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys);
std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr);
uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort);
LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort);
if (httpProxyKeys.length () > 0)
{
i2p::data::PrivateKeys keys;
LoadPrivateKeys (keys, httpProxyKeys);
localDestination = CreateNewLocalDestination (keys, false);
}
try {
m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination);
m_HttpProxy->Start();
} catch (std::exception& e) {
LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what());
}
}
bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy);
if (socksproxy) {
std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys);
std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr);
uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort);
std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr);
uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort);
LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort);
if (socksProxyKeys.length () > 0)
{
i2p::data::PrivateKeys keys;
LoadPrivateKeys (keys, socksProxyKeys);
localDestination = CreateNewLocalDestination (keys, false);
}
try {
m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, socksOutProxyAddr, socksOutProxyPort, localDestination);
m_SocksProxy->Start();
} catch (std::exception& e) {
LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what());
}
}
// I2P tunnels
ReadTunnels ();
// SAM
bool sam; i2p::config::GetOption("sam.enabled", sam);
if (sam) {
std::string samAddr; i2p::config::GetOption("sam.address", samAddr);
uint16_t samPort; i2p::config::GetOption("sam.port", samPort);
LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort);
try {
m_SamBridge = new SAMBridge (samAddr, samPort);
m_SamBridge->Start ();
} catch (std::exception& e) {
LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what());
}
}
// BOB
bool bob; i2p::config::GetOption("bob.enabled", bob);
if (bob) {
std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr);
uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort);
LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort);
try {
m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort);
m_BOBCommandChannel->Start ();
} catch (std::exception& e) {
LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what());
}
}
// I2CP
bool i2cp; i2p::config::GetOption("i2cp.enabled", i2cp);
if (i2cp)
{
std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr);
uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort);
LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort);
try
{
m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort);
m_I2CPServer->Start ();
}
catch (std::exception& e)
{
LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what());
}
}
m_AddressBook.StartResolvers ();
}
void ClientContext::Stop ()
{
if (m_HttpProxy)
{
LogPrint(eLogInfo, "Clients: stopping HTTP Proxy");
m_HttpProxy->Stop();
delete m_HttpProxy;
m_HttpProxy = nullptr;
}
if (m_SocksProxy)
{
LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy");
m_SocksProxy->Stop();
delete m_SocksProxy;
m_SocksProxy = nullptr;
}
for (auto& it: m_ClientTunnels)
{
LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first);
it.second->Stop ();
}
m_ClientTunnels.clear ();
for (auto& it: m_ServerTunnels)
{
LogPrint(eLogInfo, "Clients: stopping I2P server tunnel");
it.second->Stop ();
}
m_ServerTunnels.clear ();
if (m_SamBridge)
{
LogPrint(eLogInfo, "Clients: stopping SAM bridge");
m_SamBridge->Stop ();
delete m_SamBridge;
m_SamBridge = nullptr;
}
if (m_BOBCommandChannel)
{
LogPrint(eLogInfo, "Clients: stopping BOB command channel");
m_BOBCommandChannel->Stop ();
delete m_BOBCommandChannel;
m_BOBCommandChannel = nullptr;
}
if (m_I2CPServer)
{
LogPrint(eLogInfo, "Clients: stopping I2CP");
m_I2CPServer->Stop ();
delete m_I2CPServer;
m_I2CPServer = nullptr;
}
LogPrint(eLogInfo, "Clients: stopping AddressBook");
m_AddressBook.Stop ();
for (auto it: m_Destinations)
it.second->Stop ();
m_Destinations.clear ();
m_SharedLocalDestination = nullptr;
}
void ClientContext::ReloadConfig ()
{
ReadTunnels (); // TODO: it reads new tunnels only, should be implemented better
}
void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType)
{
std::string fullPath = i2p::fs::DataDirPath (filename);
std::ifstream s(fullPath, std::ifstream::binary);
if (s.is_open ())
{
s.seekg (0, std::ios::end);
size_t len = s.tellg();
s.seekg (0, std::ios::beg);
uint8_t * buf = new uint8_t[len];
s.read ((char *)buf, len);
keys.FromBuffer (buf, len);
delete[] buf;
LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded");
}
else
{
LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one with signature type ", sigType);
keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
size_t len = keys.GetFullLen ();
uint8_t * buf = new uint8_t[len];
len = keys.ToBuffer (buf, len);
f.write ((char *)buf, len);
delete[] buf;
LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created");
}
}
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType,
const std::map<std::string, std::string> * params)
{
i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
std::unique_lock<std::mutex> l(m_DestinationsMutex);
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
localDestination->Start ();
return localDestination;
}
void ClientContext::DeleteLocalDestination (std::shared_ptr<ClientDestination> destination)
{
if (!destination) return;
auto it = m_Destinations.find (destination->GetIdentHash ());
if (it != m_Destinations.end ())
{
auto d = it->second;
{
std::unique_lock<std::mutex> l(m_DestinationsMutex);
m_Destinations.erase (it);
}
d->Stop ();
}
}
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
const std::map<std::string, std::string> * params)
{
auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ());
if (it != m_Destinations.end ())
{
LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists");
if (!it->second->IsRunning ())
{
it->second->Start ();
return it->second;
}
return nullptr;
}
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
std::unique_lock<std::mutex> l(m_DestinationsMutex);
m_Destinations[keys.GetPublic ()->GetIdentHash ()] = localDestination;
localDestination->Start ();
return localDestination;
}
std::shared_ptr<ClientDestination> ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const
{
auto it = m_Destinations.find (destination);
if (it != m_Destinations.end ())
return it->second;
return nullptr;
}
template<typename Section, typename Type>
std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const
{
return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value));
}
template<typename Section>
void ClientContext::ReadI2CPOptions (const Section& section, std::map<std::string, std::string>& options) const
{
options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH);
options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH);
options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY);
options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY);
options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND);
}
void ClientContext::ReadTunnels ()
{
boost::property_tree::ptree pt;
std::string tunConf; i2p::config::GetOption("tunconf", tunConf);
if (tunConf == "") {
// TODO: cleanup this in 2.8.0
tunConf = i2p::fs::DataDirPath ("tunnels.cfg");
if (i2p::fs::Exists(tunConf)) {
LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf);
} else {
tunConf = i2p::fs::DataDirPath ("tunnels.conf");
}
}
LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf);
try
{
boost::property_tree::read_ini (tunConf, pt);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ());
return;
}
int numClientTunnels = 0, numServerTunnels = 0;
for (auto& section: pt)
{
std::string name = section.first;
try
{
std::string type = section.second.get<std::string> (I2P_TUNNELS_SECTION_TYPE);
if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT)
{
// mandatory params
std::string dest = section.second.get<std::string> (I2P_CLIENT_TUNNEL_DESTINATION);
int port = section.second.get<int> (I2P_CLIENT_TUNNEL_PORT);
// optional params
std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "");
std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1");
int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0);
i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
// I2CP
std::map<std::string, std::string> options;
ReadI2CPOptions (section, options);
std::shared_ptr<ClientDestination> localDestination = nullptr;
if (keys.length () > 0)
{
i2p::data::PrivateKeys k;
LoadPrivateKeys (k, keys, sigType);
localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
if (!localDestination)
localDestination = CreateNewLocalDestination (k, false, &options);
}
auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort);
if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (),
std::unique_ptr<I2PClientTunnel>(clientTunnel))).second)
{
clientTunnel->Start ();
numClientTunnels++;
}
else
LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists");
}
else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC)
{
// mandatory params
std::string host = section.second.get<std::string> (I2P_SERVER_TUNNEL_HOST);
int port = section.second.get<int> (I2P_SERVER_TUNNEL_PORT);
std::string keys = section.second.get<std::string> (I2P_SERVER_TUNNEL_KEYS);
// optional params
int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0);
std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, "");
std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, "");
std::string webircpass = section.second.get<std::string> (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, "");
bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true);
i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
// I2CP
std::map<std::string, std::string> options;
ReadI2CPOptions (section, options);
std::shared_ptr<ClientDestination> localDestination = nullptr;
i2p::data::PrivateKeys k;
LoadPrivateKeys (k, keys, sigType);
localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
if (!localDestination)
localDestination = CreateNewLocalDestination (k, true, &options);
I2PServerTunnel * serverTunnel;
if (type == I2P_TUNNELS_SECTION_TYPE_HTTP)
serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip);
else if (type == I2P_TUNNELS_SECTION_TYPE_IRC)
serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, webircpass, inPort, gzip);
else // regular server tunnel by default
serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip);
if (accessList.length () > 0)
{
std::set<i2p::data::IdentHash> idents;
size_t pos = 0, comma;
do
{
comma = accessList.find (',', pos);
i2p::data::IdentHash ident;
ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos));
idents.insert (ident);
pos = comma + 1;
}
while (comma != std::string::npos);
serverTunnel->SetAccessList (idents);
}
if (m_ServerTunnels.insert (std::make_pair (
std::make_pair (localDestination->GetIdentHash (), inPort),
std::unique_ptr<I2PServerTunnel>(serverTunnel))).second)
{
serverTunnel->Start ();
numServerTunnels++;
}
else
LogPrint (eLogError, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists");
}
else
LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ());
}
}
LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created");
LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created");
}
}
}

View File

@@ -1,102 +0,0 @@
#ifndef CLIENT_CONTEXT_H__
#define CLIENT_CONTEXT_H__
#include <map>
#include <mutex>
#include <memory>
#include <boost/asio.hpp>
#include "Destination.h"
#include "I2PService.h"
#include "HTTPProxy.h"
#include "SOCKS.h"
#include "I2PTunnel.h"
#include "SAM.h"
#include "BOB.h"
#include "I2CP.h"
#include "AddressBook.h"
namespace i2p
{
namespace client
{
const char I2P_TUNNELS_SECTION_TYPE[] = "type";
const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client";
const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server";
const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http";
const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc";
const char I2P_CLIENT_TUNNEL_PORT[] = "port";
const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address";
const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination";
const char I2P_CLIENT_TUNNEL_KEYS[] = "keys";
const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype";
const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport";
const char I2P_SERVER_TUNNEL_HOST[] = "host";
const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride";
const char I2P_SERVER_TUNNEL_PORT[] = "port";
const char I2P_SERVER_TUNNEL_KEYS[] = "keys";
const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype";
const char I2P_SERVER_TUNNEL_INPORT[] = "inport";
const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist";
const char I2P_SERVER_TUNNEL_GZIP[] = "gzip";
const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword";
class ClientContext
{
public:
ClientContext ();
~ClientContext ();
void Start ();
void Stop ();
void ReloadConfig ();
std::shared_ptr<ClientDestination> GetSharedLocalDestination () const { return m_SharedLocalDestination; };
std::shared_ptr<ClientDestination> CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1,
const std::map<std::string, std::string> * params = nullptr); // transient
std::shared_ptr<ClientDestination> CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
const std::map<std::string, std::string> * params = nullptr);
void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination);
std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const;
void LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
AddressBook& GetAddressBook () { return m_AddressBook; };
const SAMBridge * GetSAMBridge () const { return m_SamBridge; };
private:
void ReadTunnels ();
template<typename Section, typename Type>
std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const;
template<typename Section>
void ReadI2CPOptions (const Section& section, std::map<std::string, std::string>& options) const;
private:
std::mutex m_DestinationsMutex;
std::map<i2p::data::IdentHash, std::shared_ptr<ClientDestination> > m_Destinations;
std::shared_ptr<ClientDestination> m_SharedLocalDestination;
AddressBook m_AddressBook;
i2p::proxy::HTTPProxy * m_HttpProxy;
i2p::proxy::SOCKSProxy * m_SocksProxy;
std::map<boost::asio::ip::tcp::endpoint, std::unique_ptr<I2PClientTunnel> > m_ClientTunnels; // local endpoint->tunnel
std::map<std::pair<i2p::data::IdentHash, int>, std::unique_ptr<I2PServerTunnel> > m_ServerTunnels; // <destination,port>->tunnel
SAMBridge * m_SamBridge;
BOBCommandChannel * m_BOBCommandChannel;
I2CPServer * m_I2CPServer;
public:
// for HTTP
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; };
const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; };
};
extern ClientContext context;
}
}
#endif

View File

@@ -1,276 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <boost/program_options/cmdline.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#include "Config.h"
#include "version.h"
using namespace boost::program_options;
namespace i2p {
namespace config {
options_description m_OptionsDesc;
variables_map m_Options;
/* list of renamed options */
std::map<std::string, std::string> remapped_options = {
{ "tunnelscfg", "tunconf" },
{ "v6", "ipv6" },
{ "httpaddress", "http.address" },
{ "httpport", "http.port" },
{ "httpproxyaddress", "httpproxy.address" },
{ "httpproxyport", "httpproxy.port" },
{ "socksproxyaddress", "socksproxy.address" },
{ "socksproxyport", "socksproxy.port" },
{ "samaddress", "sam.address" },
{ "samport", "sam.port" },
{ "bobaddress", "bob.address" },
{ "bobport", "bob.port" },
{ "i2pcontroladdress", "i2pcontrol.address" },
{ "i2pcontroladdress", "i2pcontrol.port" },
{ "proxykeys", "httpproxy.keys" },
};
/* list of options, that loose their argument and become simple switch */
std::set<std::string> boolean_options = {
"daemon", "floodfill", "notransit", "service", "ipv6"
};
/* this function is a solid piece of shit, remove it after 2.6.0 */
std::pair<std::string, std::string> old_syntax_parser(const std::string& s) {
std::string name = "";
std::string value = "";
std::size_t pos = 0;
/* shortcuts -- -h */
if (s.length() == 2 && s.at(0) == '-' && s.at(1) != '-')
return make_pair(s.substr(1), "");
/* old-style -- -log, /log, etc */
if (s.at(0) == '/' || (s.at(0) == '-' && s.at(1) != '-')) {
if ((pos = s.find_first_of("= ")) != std::string::npos) {
name = s.substr(1, pos - 1);
value = s.substr(pos + 1);
} else {
name = s.substr(1, pos);
value = "";
}
if (boolean_options.count(name) > 0 && value != "")
std::cerr << "args: don't give an argument to switch option: " << s << std::endl;
if (m_OptionsDesc.find_nothrow(name, false)) {
std::cerr << "args: option " << s << " style is DEPRECATED, use --" << name << " instead" << std::endl;
return std::make_pair(name, value);
}
if (remapped_options.count(name) > 0) {
name = remapped_options[name];
std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl;
return std::make_pair(name, value);
} /* else */
}
/* long options -- --help */
if (s.substr(0, 2) == "--") {
if ((pos = s.find_first_of("= ")) != std::string::npos) {
name = s.substr(2, pos - 2);
value = s.substr(pos + 1);
} else {
name = s.substr(2, pos);
value = "";
}
if (boolean_options.count(name) > 0 && value != "") {
std::cerr << "args: don't give an argument to switch option: " << s << std::endl;
value = "";
}
if (m_OptionsDesc.find_nothrow(name, false))
return std::make_pair(name, value);
if (remapped_options.count(name) > 0) {
name = remapped_options[name];
std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl;
return std::make_pair(name, value);
} /* else */
}
std::cerr << "args: unknown option -- " << s << std::endl;
return std::make_pair("", "");
}
void Init() {
options_description general("General options");
general.add_options()
("help", "Show this message")
("conf", value<std::string>()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)")
("tunconf", value<std::string>()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)")
("pidfile", value<std::string>()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)")
("log", value<std::string>()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)")
("logfile", value<std::string>()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)")
("loglevel", value<std::string>()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)")
("family", value<std::string>()->default_value(""), "Specify a family, router belongs to")
("datadir", value<std::string>()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)")
("host", value<std::string>()->default_value("0.0.0.0"), "External IP")
("port", value<uint16_t>()->default_value(0), "Port to listen for incoming connections (default: auto)")
("ipv4", value<bool>()->zero_tokens()->default_value(true), "Enable communication through ipv4")
("ipv6", value<bool>()->zero_tokens()->default_value(false), "Enable communication through ipv6")
("daemon", value<bool>()->zero_tokens()->default_value(false), "Router will go to background after start")
("service", value<bool>()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'")
("notransit", value<bool>()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup")
("floodfill", value<bool>()->zero_tokens()->default_value(false), "Router will be floodfill")
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)")
#ifdef _WIN32
("svcctl", value<std::string>()->default_value(""), "Windows service management ('install' or 'remove')")
("insomnia", value<bool>()->zero_tokens()->default_value(false), "Prevent system from sleeping")
("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something
#endif
;
options_description limits("Limits options");
limits.add_options()
("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)")
;
options_description httpserver("HTTP Server options");
httpserver.add_options()
("http.enabled", value<bool>()->default_value(true), "Enable or disable webconsole")
("http.address", value<std::string>()->default_value("127.0.0.1"), "Webconsole listen address")
("http.port", value<uint16_t>()->default_value(7070), "Webconsole listen port")
("http.auth", value<bool>()->default_value(false), "Enable Basic HTTP auth for webconsole")
("http.user", value<std::string>()->default_value("i2pd"), "Username for basic auth")
("http.pass", value<std::string>()->default_value(""), "Password for basic auth (default: random, see logs)")
;
options_description httpproxy("HTTP Proxy options");
httpproxy.add_options()
("httpproxy.enabled", value<bool>()->default_value(true), "Enable or disable HTTP Proxy")
("httpproxy.address", value<std::string>()->default_value("127.0.0.1"), "HTTP Proxy listen address")
("httpproxy.port", value<uint16_t>()->default_value(4444), "HTTP Proxy listen port")
("httpproxy.keys", value<std::string>()->default_value(""), "File to persist HTTP Proxy keys")
;
options_description socksproxy("SOCKS Proxy options");
socksproxy.add_options()
("socksproxy.enabled", value<bool>()->default_value(true), "Enable or disable SOCKS Proxy")
("socksproxy.address", value<std::string>()->default_value("127.0.0.1"), "SOCKS Proxy listen address")
("socksproxy.port", value<uint16_t>()->default_value(4447), "SOCKS Proxy listen port")
("socksproxy.keys", value<std::string>()->default_value(""), "File to persist SOCKS Proxy keys")
("socksproxy.outproxy", value<std::string>()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy")
("socksproxy.outproxyport", value<uint16_t>()->default_value(9050), "Upstream outproxy port for SOCKS Proxy")
;
options_description sam("SAM bridge options");
sam.add_options()
("sam.enabled", value<bool>()->default_value(false), "Enable or disable SAM Application bridge")
("sam.address", value<std::string>()->default_value("127.0.0.1"), "SAM listen address")
("sam.port", value<uint16_t>()->default_value(7656), "SAM listen port")
;
options_description bob("BOB options");
bob.add_options()
("bob.enabled", value<bool>()->default_value(false), "Enable or disable BOB command channel")
("bob.address", value<std::string>()->default_value("127.0.0.1"), "BOB listen address")
("bob.port", value<uint16_t>()->default_value(2827), "BOB listen port")
;
options_description i2cp("I2CP options");
i2cp.add_options()
("i2cp.enabled", value<bool>()->default_value(false), "Enable or disable I2CP")
("i2cp.address", value<std::string>()->default_value("127.0.0.1"), "I2CP listen address")
("i2cp.port", value<uint16_t>()->default_value(7654), "I2CP listen port")
;
options_description i2pcontrol("I2PControl options");
i2pcontrol.add_options()
("i2pcontrol.enabled", value<bool>()->default_value(false), "Enable or disable I2P Control Protocol")
("i2pcontrol.address", value<std::string>()->default_value("127.0.0.1"), "I2PCP listen address")
("i2pcontrol.port", value<uint16_t>()->default_value(7650), "I2PCP listen port")
("i2pcontrol.password", value<std::string>()->default_value("itoopie"), "I2PCP access password")
("i2pcontrol.cert", value<std::string>()->default_value("i2pcontrol.crt.pem"), "I2PCP connection cerificate")
("i2pcontrol.key", value<std::string>()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key")
;
options_description precomputation("Precomputation options");
precomputation.add_options()
("precomputation.elgamal",
#if defined(__x86_64__)
value<bool>()->default_value(false),
#else
value<bool>()->default_value(true),
#endif
"Enable or disable elgamal precomputation table")
;
m_OptionsDesc
.add(general)
.add(limits)
.add(httpserver)
.add(httpproxy)
.add(socksproxy)
.add(sam)
.add(bob)
.add(i2cp)
.add(i2pcontrol)
.add(precomputation)
;
}
void ParseCmdline(int argc, char* argv[]) {
try {
auto style = boost::program_options::command_line_style::unix_style
| boost::program_options::command_line_style::allow_long_disguise;
style &= ~ boost::program_options::command_line_style::allow_guessing;
store(parse_command_line(argc, argv, m_OptionsDesc, style, old_syntax_parser), m_Options);
} catch (boost::program_options::error& e) {
std::cerr << "args: " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
if (m_Options.count("help") || m_Options.count("h")) {
std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl;
std::cout << m_OptionsDesc;
exit(EXIT_SUCCESS);
}
}
void ParseConfig(const std::string& path) {
if (path == "") return;
std::ifstream config(path, std::ios::in);
if (!config.is_open())
{
std::cerr << "missing/unreadable config file: " << path << std::endl;
exit(EXIT_FAILURE);
}
try
{
store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options);
}
catch (boost::program_options::error& e)
{
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
};
}
void Finalize() {
notify(m_Options);
}
bool IsDefault(const char *name) {
if (!m_Options.count(name))
throw "try to check non-existent option";
if (m_Options[name].defaulted())
return true;
return false;
}
} // namespace config
} // namespace i2p

107
Config.h
View File

@@ -1,107 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <string>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
/**
* Functions to parse and store i2pd parameters
*
* General usage flow:
* Init() -- early as possible
* ParseCmdline() -- somewhere close to main()
* ParseConfig() -- after detecting path to config
* Finalize() -- right after all Parse*() functions called
* GetOption() -- may be called after Finalize()
*/
namespace i2p {
namespace config {
extern boost::program_options::variables_map m_Options;
/**
* @brief Initialize list of acceptable parameters
*
* Should be called before any Parse* functions.
*/
void Init();
/**
* @brief Parse cmdline parameters, and show help if requested
* @param argc Cmdline arguments count, should be passed from main().
* @param argv Cmdline parameters array, should be passed from main()
*
* If --help is given in parameters, shows it's list with description
* terminates the program with exitcode 0.
*
* In case of parameter misuse boost throws an exception.
* We internally handle type boost::program_options::unknown_option,
* and then terminate program with exitcode 1.
*
* Other exceptions will be passed to higher level.
*/
void ParseCmdline(int argc, char* argv[]);
/**
* @brief Load and parse given config file
* @param path Path to config file
*
* If error occured when opening file path is points to,
* we show the error message and terminate program.
*
* In case of parameter misuse boost throws an exception.
* We internally handle type boost::program_options::unknown_option,
* and then terminate program with exitcode 1.
*
* Other exceptions will be passed to higher level.
*/
void ParseConfig(const std::string& path);
/**
* @brief Used to combine options from cmdline, config and default values
*/
void Finalize();
/* @brief Accessor to parameters by name
* @param name Name of the requested parameter
* @param value Variable where to store option
* @return this function returns false if parameter not found
*
* Example: uint16_t port; GetOption("sam.port", port);
*/
template<typename T>
bool GetOption(const char *name, T& value) {
if (!m_Options.count(name))
return false;
value = m_Options[name].as<T>();
return true;
}
/**
* @brief Set value of given parameter
* @param name Name of settable parameter
* @param value New parameter value
* @return true if value set up successful, false otherwise
*
* Example: uint16_t port = 2827; SetOption("bob.port", port);
*/
template<typename T>
bool SetOption(const char *name, const T& value) {
if (!m_Options.count(name))
return false;
m_Options.at(name).value() = value;
notify(m_Options);
return true;
}
/**
* @brief Check is value explicitly given or default
* @param name Name of checked parameter
* @return true if value set to default, false othervise
*/
bool IsDefault(const char *name);
}
}
#endif // CONFIG_H

View File

@@ -1,839 +0,0 @@
#include <string.h>
#include <string>
#include <vector>
#include <mutex>
#include <memory>
#include <openssl/dh.h>
#include <openssl/md5.h>
#include <openssl/crypto.h>
#include "TunnelBase.h"
#include <openssl/ssl.h>
#include "Log.h"
#include "Crypto.h"
namespace i2p
{
namespace crypto
{
const uint8_t elgp_[256]=
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05,
0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB,
0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04,
0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F,
0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18,
0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
const int elgg_ = 2;
const uint8_t dsap_[128]=
{
0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c,
0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15,
0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23,
0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c,
0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8,
0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c,
0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11,
0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
};
const uint8_t dsaq_[20]=
{
0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
0x68, 0x40, 0x46, 0xb7
};
const uint8_t dsag_[128]=
{
0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0,
0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81,
0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2,
0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52,
0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc,
0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a,
0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c,
0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
};
const int rsae_ = 65537;
struct CryptoConstants
{
// DH/ElGamal
BIGNUM * elgp;
BIGNUM * elgg;
// DSA
BIGNUM * dsap;
BIGNUM * dsaq;
BIGNUM * dsag;
// RSA
BIGNUM * rsae;
CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_,
const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_)
{
elgp = BN_new ();
BN_bin2bn (elgp_, 256, elgp);
elgg = BN_new ();
BN_set_word (elgg, elgg_);
dsap = BN_new ();
BN_bin2bn (dsap_, 128, dsap);
dsaq = BN_new ();
BN_bin2bn (dsaq_, 20, dsaq);
dsag = BN_new ();
BN_bin2bn (dsag_, 128, dsag);
rsae = BN_new ();
BN_set_word (rsae, rsae_);
}
~CryptoConstants ()
{
BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae);
}
};
static const CryptoConstants& GetCryptoConstants ()
{
static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_);
return cryptoConstants;
}
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len)
{
int offset = len - BN_num_bytes (bn);
if (offset < 0) return false;
BN_bn2bin (bn, buf + offset);
memset (buf, 0, offset);
return true;
}
// RSA
#define rsae GetCryptoConstants ().rsae
const BIGNUM * GetRSAE ()
{
return rsae;
}
// DSA
#define dsap GetCryptoConstants ().dsap
#define dsaq GetCryptoConstants ().dsaq
#define dsag GetCryptoConstants ().dsag
DSA * CreateDSA ()
{
DSA * dsa = DSA_new ();
dsa->p = BN_dup (dsap);
dsa->q = BN_dup (dsaq);
dsa->g = BN_dup (dsag);
dsa->priv_key = NULL;
dsa->pub_key = NULL;
return dsa;
}
// DH/ElGamal
const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226;
const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1;
const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048;
const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8;
#define elgp GetCryptoConstants ().elgp
#define elgg GetCryptoConstants ().elgg
static BN_MONT_CTX * g_MontCtx = nullptr;
static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums
{
if (len <= 0) return;
BN_CTX * ctx = BN_CTX_new ();
g_MontCtx = BN_MONT_CTX_new ();
BN_MONT_CTX_set (g_MontCtx, elgp, ctx);
auto montCtx = BN_MONT_CTX_new ();
BN_MONT_CTX_copy (montCtx, g_MontCtx);
for (int i = 0; i < len; i++)
{
table[i][0] = BN_new ();
if (!i)
BN_to_montgomery (table[0][0], elgg, montCtx, ctx);
else
BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx);
for (int j = 1; j < 255; j++)
{
table[i][j] = BN_new ();
BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx);
}
}
BN_MONT_CTX_free (montCtx);
BN_CTX_free (ctx);
}
static void DestroyElggTable (BIGNUM * table[][255], int len)
{
for (int i = 0; i < len; i++)
for (int j = 0; j < 255; j++)
{
BN_free (table[i][j]);
table[i][j] = nullptr;
}
BN_MONT_CTX_free (g_MontCtx);
}
static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx)
// exp is in Big Endian
{
if (len <= 0) return nullptr;
auto montCtx = BN_MONT_CTX_new ();
BN_MONT_CTX_copy (montCtx, g_MontCtx);
BIGNUM * res = nullptr;
for (int i = 0; i < len; i++)
{
if (res)
{
if (exp[i])
BN_mod_mul_montgomery (res, res, table[len-1-i][exp[i]-1], montCtx, ctx);
}
else if (exp[i])
res = BN_dup (table[len-i-1][exp[i]-1]);
}
if (res)
BN_from_montgomery (res, res, montCtx, ctx);
BN_MONT_CTX_free (montCtx);
return res;
}
static BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx)
{
auto len = BN_num_bytes (exp);
uint8_t * buf = new uint8_t[len];
BN_bn2bin (exp, buf);
auto ret = ElggPow (buf, len, table, ctx);
delete[] buf;
return ret;
}
static BIGNUM * (* g_ElggTable)[255] = nullptr;
// DH
DHKeys::DHKeys (): m_IsUpdated (true)
{
m_DH = DH_new ();
m_DH->p = BN_dup (elgp);
m_DH->g = BN_dup (elgg);
m_DH->priv_key = NULL;
m_DH->pub_key = NULL;
}
DHKeys::~DHKeys ()
{
DH_free (m_DH);
}
void DHKeys::GenerateKeys (uint8_t * priv, uint8_t * pub)
{
if (m_DH->priv_key) { BN_free (m_DH->priv_key); m_DH->priv_key = NULL; };
if (m_DH->pub_key) { BN_free (m_DH->pub_key); m_DH->pub_key = NULL; };
#if !defined(__x86_64__) // use short exponent for non x64
m_DH->priv_key = BN_new ();
BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1);
#endif
if (g_ElggTable)
{
#if defined(__x86_64__)
m_DH->priv_key = BN_new ();
BN_rand (m_DH->priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1);
#endif
auto ctx = BN_CTX_new ();
m_DH->pub_key = ElggPow (m_DH->priv_key, g_ElggTable, ctx);
BN_CTX_free (ctx);
}
else
DH_generate_key (m_DH);
if (priv) bn2buf (m_DH->priv_key, priv, 256);
if (pub) bn2buf (m_DH->pub_key, pub, 256);
m_IsUpdated = true;
}
const uint8_t * DHKeys::GetPublicKey ()
{
if (m_IsUpdated)
{
bn2buf (m_DH->pub_key, m_PublicKey, 256);
BN_free (m_DH->pub_key); m_DH->pub_key = NULL;
m_IsUpdated= false;
}
return m_PublicKey;
}
void DHKeys::Agree (const uint8_t * pub, uint8_t * shared)
{
BIGNUM * pk = BN_bin2bn (pub, 256, NULL);
DH_compute_key (shared, pk, m_DH);
BN_free (pk);
}
// ElGamal
ElGamalEncryption::ElGamalEncryption (const uint8_t * key)
{
ctx = BN_CTX_new ();
// select random k
BIGNUM * k = BN_new ();
#if defined(__x86_64__)
BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64
#else
BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits
#endif
// calculate a
if (g_ElggTable)
a = ElggPow (k, g_ElggTable, ctx);
else
{
a = BN_new ();
BN_mod_exp (a, elgg, k, elgp, ctx);
}
BIGNUM * y = BN_new ();
BN_bin2bn (key, 256, y);
// calculate b1
b1 = BN_new ();
BN_mod_exp (b1, y, k, elgp, ctx);
BN_free (y);
BN_free (k);
}
ElGamalEncryption::~ElGamalEncryption ()
{
BN_CTX_free (ctx);
BN_free (a);
BN_free (b1);
}
void ElGamalEncryption::Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding) const
{
// create m
uint8_t m[255];
m[0] = 0xFF;
memcpy (m+33, data, len);
SHA256 (m+33, 222, m+1);
// calculate b = b1*m mod p
BIGNUM * b = BN_new ();
BN_bin2bn (m, 255, b);
BN_mod_mul (b, b1, b, elgp, ctx);
// copy a and b
if (zeroPadding)
{
encrypted[0] = 0;
bn2buf (a, encrypted + 1, 256);
encrypted[257] = 0;
bn2buf (b, encrypted + 258, 256);
}
else
{
bn2buf (a, encrypted, 256);
bn2buf (b, encrypted + 256, 256);
}
BN_free (b);
}
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted,
uint8_t * data, bool zeroPadding)
{
BN_CTX * ctx = BN_CTX_new ();
BIGNUM * x = BN_new (), * a = BN_new (), * b = BN_new ();
BN_bin2bn (key, 256, x);
BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1
BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a);
BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b);
// m = b*(a^x mod p) mod p
BN_mod_exp (x, a, x, elgp, ctx);
BN_mod_mul (b, b, x, elgp, ctx);
uint8_t m[255];
bn2buf (b, m, 255);
BN_free (x); BN_free (a); BN_free (b);
BN_CTX_free (ctx);
uint8_t hash[32];
SHA256 (m + 33, 222, hash);
if (memcmp (m + 1, hash, 32))
{
LogPrint (eLogError, "ElGamal decrypt hash doesn't match");
return false;
}
memcpy (data, m + 33, 222);
return true;
}
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub)
{
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
RAND_bytes (priv, 256);
#else
// lower 226 bits (28 bytes and 2 bits) only. short exponent
auto numBytes = (ELGAMAL_SHORT_EXPONENT_NUM_BITS)/8 + 1; // 29
auto numZeroBytes = 256 - numBytes;
RAND_bytes (priv + numZeroBytes, numBytes);
memset (priv, 0, numZeroBytes);
priv[numZeroBytes] &= 0x03;
#endif
BN_CTX * ctx = BN_CTX_new ();
BIGNUM * p = BN_new ();
BN_bin2bn (priv, 256, p);
BN_mod_exp (p, elgg, p, elgp, ctx);
bn2buf (p, pub, 256);
BN_free (p);
BN_CTX_free (ctx);
}
// HMAC
const uint64_t IPAD = 0x3636363636363636;
const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C;
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest)
// key is 32 bytes
// digest is 16 bytes
// block size is 64 bytes
{
uint64_t buf[256];
// ikeypad
buf[0] = key.GetLL ()[0] ^ IPAD;
buf[1] = key.GetLL ()[1] ^ IPAD;
buf[2] = key.GetLL ()[2] ^ IPAD;
buf[3] = key.GetLL ()[3] ^ IPAD;
buf[4] = IPAD;
buf[5] = IPAD;
buf[6] = IPAD;
buf[7] = IPAD;
// concatenate with msg
memcpy (buf + 8, msg, len);
// calculate first hash
uint8_t hash[16]; // MD5
MD5((uint8_t *)buf, len + 64, hash);
// okeypad
buf[0] = key.GetLL ()[0] ^ OPAD;
buf[1] = key.GetLL ()[1] ^ OPAD;
buf[2] = key.GetLL ()[2] ^ OPAD;
buf[3] = key.GetLL ()[3] ^ OPAD;
buf[4] = OPAD;
buf[5] = OPAD;
buf[6] = OPAD;
buf[7] = OPAD;
// copy first hash after okeypad
memcpy (buf + 8, hash, 16);
// fill next 16 bytes with zeros (first hash size assumed 32 bytes in I2P)
memset (buf + 10, 0, 16);
// calculate digest
MD5((uint8_t *)buf, 96, digest);
}
// AES
#ifdef AESNI
#define KeyExpansion256(round0,round1) \
"pshufd $0xff, %%xmm2, %%xmm2 \n" \
"movaps %%xmm1, %%xmm4 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pxor %%xmm2, %%xmm1 \n" \
"movaps %%xmm1, "#round0"(%[sched]) \n" \
"aeskeygenassist $0, %%xmm1, %%xmm4 \n" \
"pshufd $0xaa, %%xmm4, %%xmm2 \n" \
"movaps %%xmm3, %%xmm4 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pxor %%xmm2, %%xmm3 \n" \
"movaps %%xmm3, "#round1"(%[sched]) \n"
void ECBCryptoAESNI::ExpandKey (const AESKey& key)
{
__asm__
(
"movups (%[key]), %%xmm1 \n"
"movups 16(%[key]), %%xmm3 \n"
"movaps %%xmm1, (%[sched]) \n"
"movaps %%xmm3, 16(%[sched]) \n"
"aeskeygenassist $1, %%xmm3, %%xmm2 \n"
KeyExpansion256(32,48)
"aeskeygenassist $2, %%xmm3, %%xmm2 \n"
KeyExpansion256(64,80)
"aeskeygenassist $4, %%xmm3, %%xmm2 \n"
KeyExpansion256(96,112)
"aeskeygenassist $8, %%xmm3, %%xmm2 \n"
KeyExpansion256(128,144)
"aeskeygenassist $16, %%xmm3, %%xmm2 \n"
KeyExpansion256(160,176)
"aeskeygenassist $32, %%xmm3, %%xmm2 \n"
KeyExpansion256(192,208)
"aeskeygenassist $64, %%xmm3, %%xmm2 \n"
// key expansion final
"pshufd $0xff, %%xmm2, %%xmm2 \n"
"movaps %%xmm1, %%xmm4 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pxor %%xmm2, %%xmm1 \n"
"movups %%xmm1, 224(%[sched]) \n"
: // output
: [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input
: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged
);
}
#define EncryptAES256(sched) \
"pxor (%["#sched"]), %%xmm0 \n" \
"aesenc 16(%["#sched"]), %%xmm0 \n" \
"aesenc 32(%["#sched"]), %%xmm0 \n" \
"aesenc 48(%["#sched"]), %%xmm0 \n" \
"aesenc 64(%["#sched"]), %%xmm0 \n" \
"aesenc 80(%["#sched"]), %%xmm0 \n" \
"aesenc 96(%["#sched"]), %%xmm0 \n" \
"aesenc 112(%["#sched"]), %%xmm0 \n" \
"aesenc 128(%["#sched"]), %%xmm0 \n" \
"aesenc 144(%["#sched"]), %%xmm0 \n" \
"aesenc 160(%["#sched"]), %%xmm0 \n" \
"aesenc 176(%["#sched"]), %%xmm0 \n" \
"aesenc 192(%["#sched"]), %%xmm0 \n" \
"aesenc 208(%["#sched"]), %%xmm0 \n" \
"aesenclast 224(%["#sched"]), %%xmm0 \n"
void ECBEncryptionAESNI::Encrypt (const ChipherBlock * in, ChipherBlock * out)
{
__asm__
(
"movups (%[in]), %%xmm0 \n"
EncryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
);
}
#define DecryptAES256(sched) \
"pxor 224(%["#sched"]), %%xmm0 \n" \
"aesdec 208(%["#sched"]), %%xmm0 \n" \
"aesdec 192(%["#sched"]), %%xmm0 \n" \
"aesdec 176(%["#sched"]), %%xmm0 \n" \
"aesdec 160(%["#sched"]), %%xmm0 \n" \
"aesdec 144(%["#sched"]), %%xmm0 \n" \
"aesdec 128(%["#sched"]), %%xmm0 \n" \
"aesdec 112(%["#sched"]), %%xmm0 \n" \
"aesdec 96(%["#sched"]), %%xmm0 \n" \
"aesdec 80(%["#sched"]), %%xmm0 \n" \
"aesdec 64(%["#sched"]), %%xmm0 \n" \
"aesdec 48(%["#sched"]), %%xmm0 \n" \
"aesdec 32(%["#sched"]), %%xmm0 \n" \
"aesdec 16(%["#sched"]), %%xmm0 \n" \
"aesdeclast (%["#sched"]), %%xmm0 \n"
void ECBDecryptionAESNI::Decrypt (const ChipherBlock * in, ChipherBlock * out)
{
__asm__
(
"movups (%[in]), %%xmm0 \n"
DecryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
);
}
#define CallAESIMC(offset) \
"movaps "#offset"(%[shed]), %%xmm0 \n" \
"aesimc %%xmm0, %%xmm0 \n" \
"movaps %%xmm0, "#offset"(%[shed]) \n"
void ECBDecryptionAESNI::SetKey (const AESKey& key)
{
ExpandKey (key); // expand encryption key first
// then invert it using aesimc
__asm__
(
CallAESIMC(16)
CallAESIMC(32)
CallAESIMC(48)
CallAESIMC(64)
CallAESIMC(80)
CallAESIMC(96)
CallAESIMC(112)
CallAESIMC(128)
CallAESIMC(144)
CallAESIMC(160)
CallAESIMC(176)
CallAESIMC(192)
CallAESIMC(208)
: : [shed]"r"(GetKeySchedule ()) : "%xmm0", "memory"
);
}
#endif
void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"1: \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched)
"movaps %%xmm0, %%xmm1 \n"
"movups %%xmm0, (%[out]) \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"dec %[num] \n"
"jnz 1b \n"
"movups %%xmm1, (%[iv]) \n"
:
: [iv]"r"(&m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
: "%xmm0", "%xmm1", "cc", "memory"
);
#else
for (int i = 0; i < numBlocks; i++)
{
m_LastBlock ^= in[i];
m_ECBEncryption.Encrypt (&m_LastBlock, &m_LastBlock);
out[i] = m_LastBlock;
}
#endif
}
void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out)
{
// len/16
int numBlocks = len >> 4;
if (numBlocks > 0)
Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
}
void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
"movups %%xmm0, (%[iv]) \n"
:
: [iv]"r"(&m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out)
: "%xmm0", "%xmm1", "memory"
);
#else
Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
#endif
}
void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"1: \n"
"movups (%[in]), %%xmm0 \n"
"movaps %%xmm0, %%xmm2 \n"
DecryptAES256(sched)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
"movaps %%xmm2, %%xmm1 \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"dec %[num] \n"
"jnz 1b \n"
"movups %%xmm1, (%[iv]) \n"
:
: [iv]"r"(&m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
);
#else
for (int i = 0; i < numBlocks; i++)
{
ChipherBlock tmp = in[i];
m_ECBDecryption.Decrypt (in + i, out + i);
out[i] ^= m_IV;
m_IV = tmp;
}
#endif
}
void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out)
{
int numBlocks = len >> 4;
if (numBlocks > 0)
Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
}
void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"movups (%[in]), %%xmm0 \n"
"movups %%xmm0, (%[iv]) \n"
DecryptAES256(sched)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
:
: [iv]"r"(&m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out)
: "%xmm0", "%xmm1", "memory"
);
#else
Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
#endif
}
void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
// encrypt IV
"movups (%[in]), %%xmm0 \n"
EncryptAES256(sched_iv)
"movaps %%xmm0, %%xmm1 \n"
// double IV encryption
EncryptAES256(sched_iv)
"movups %%xmm0, (%[out]) \n"
// encrypt data, IV is xmm1
"1: \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched_l)
"movaps %%xmm0, %%xmm1 \n"
"movups %%xmm0, (%[out]) \n"
"dec %[num] \n"
"jnz 1b \n"
:
: [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
: "%xmm0", "%xmm1", "cc", "memory"
);
#else
m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
m_LayerEncryption.SetIV (out);
m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
#endif
}
void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
#ifdef AESNI
__asm__
(
// decrypt IV
"movups (%[in]), %%xmm0 \n"
DecryptAES256(sched_iv)
"movaps %%xmm0, %%xmm1 \n"
// double IV encryption
DecryptAES256(sched_iv)
"movups %%xmm0, (%[out]) \n"
// decrypt data, IV is xmm1
"1: \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"movups (%[in]), %%xmm0 \n"
"movaps %%xmm0, %%xmm2 \n"
DecryptAES256(sched_l)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
"movaps %%xmm2, %%xmm1 \n"
"dec %[num] \n"
"jnz 1b \n"
:
: [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
);
#else
m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
m_LayerDecryption.SetIV (out);
m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
#endif
}
/* std::vector <std::unique_ptr<std::mutex> > m_OpenSSLMutexes;
static void OpensslLockingCallback(int mode, int type, const char * file, int line)
{
if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ())
{
if (mode & CRYPTO_LOCK)
m_OpenSSLMutexes[type]->lock ();
else
m_OpenSSLMutexes[type]->unlock ();
}
}*/
void InitCrypto (bool precomputation)
{
SSL_library_init ();
/* auto numLocks = CRYPTO_num_locks();
for (int i = 0; i < numLocks; i++)
m_OpenSSLMutexes.emplace_back (new std::mutex);
CRYPTO_set_locking_callback (OpensslLockingCallback);*/
if (precomputation)
{
#if defined(__x86_64__)
g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255];
PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES);
#else
g_ElggTable = new BIGNUM * [ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255];
PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES);
#endif
}
}
void TerminateCrypto ()
{
if (g_ElggTable)
{
DestroyElggTable (g_ElggTable,
#if defined(__x86_64__)
ELGAMAL_FULL_EXPONENT_NUM_BYTES
#else
ELGAMAL_SHORT_EXPONENT_NUM_BYTES
#endif
);
delete[] g_ElggTable; g_ElggTable = nullptr;
}
/* CRYPTO_set_locking_callback (nullptr);
m_OpenSSLMutexes.clear ();*/
}
}
}

283
Crypto.h
View File

@@ -1,283 +0,0 @@
#ifndef CRYPTO_H__
#define CRYPTO_H__
#include <inttypes.h>
#include <string>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/aes.h>
#include <openssl/dsa.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include "Base.h"
namespace i2p
{
namespace crypto
{
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
// DSA
DSA * CreateDSA ();
// RSA
const BIGNUM * GetRSAE ();
// DH
class DHKeys
{
public:
DHKeys ();
~DHKeys ();
void GenerateKeys (uint8_t * priv = nullptr, uint8_t * pub = nullptr);
const uint8_t * GetPublicKey ();
void Agree (const uint8_t * pub, uint8_t * shared);
private:
DH * m_DH;
uint8_t m_PublicKey[256];
bool m_IsUpdated;
};
// ElGamal
class ElGamalEncryption
{
public:
ElGamalEncryption (const uint8_t * key);
~ElGamalEncryption ();
void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) const;
private:
BN_CTX * ctx;
BIGNUM * a, * b1;
};
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, bool zeroPadding = false);
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub);
// HMAC
typedef i2p::data::Tag<32> MACKey;
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest);
// AES
struct ChipherBlock
{
uint8_t buf[16];
void operator^=(const ChipherBlock& other) // XOR
{
#if defined(__x86_64__) || defined(__SSE__) // for Intel x84 or with SSE
__asm__
(
"movups (%[buf]), %%xmm0 \n"
"movups (%[other]), %%xmm1 \n"
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[buf]) \n"
:
: [buf]"r"(buf), [other]"r"(other.buf)
: "%xmm0", "%xmm1", "memory"
);
#else
// TODO: implement it better
for (int i = 0; i < 16; i++)
buf[i] ^= other.buf[i];
#endif
}
};
typedef i2p::data::Tag<32> AESKey;
template<size_t sz>
class AESAlignedBuffer // 16 bytes alignment
{
public:
AESAlignedBuffer ()
{
m_Buf = m_UnalignedBuffer;
uint8_t rem = ((size_t)m_Buf) & 0x0f;
if (rem)
m_Buf += (16 - rem);
}
operator uint8_t * () { return m_Buf; };
operator const uint8_t * () const { return m_Buf; };
private:
uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
uint8_t * m_Buf;
};
#ifdef AESNI
class ECBCryptoAESNI
{
public:
uint8_t * GetKeySchedule () { return m_KeySchedule; };
protected:
void ExpandKey (const AESKey& key);
private:
AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes
};
class ECBEncryptionAESNI: public ECBCryptoAESNI
{
public:
void SetKey (const AESKey& key) { ExpandKey (key); };
void Encrypt (const ChipherBlock * in, ChipherBlock * out);
};
class ECBDecryptionAESNI: public ECBCryptoAESNI
{
public:
void SetKey (const AESKey& key);
void Decrypt (const ChipherBlock * in, ChipherBlock * out);
};
typedef ECBEncryptionAESNI ECBEncryption;
typedef ECBDecryptionAESNI ECBDecryption;
#else // use openssl
class ECBEncryption
{
public:
void SetKey (const AESKey& key)
{
AES_set_encrypt_key (key, 256, &m_Key);
}
void Encrypt (const ChipherBlock * in, ChipherBlock * out)
{
AES_encrypt (in->buf, out->buf, &m_Key);
}
private:
AES_KEY m_Key;
};
class ECBDecryption
{
public:
void SetKey (const AESKey& key)
{
AES_set_decrypt_key (key, 256, &m_Key);
}
void Decrypt (const ChipherBlock * in, ChipherBlock * out)
{
AES_decrypt (in->buf, out->buf, &m_Key);
}
private:
AES_KEY m_Key;
};
#endif
class CBCEncryption
{
public:
CBCEncryption () { memset (m_LastBlock.buf, 0, 16); };
void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy (m_LastBlock.buf, iv, 16); }; // 16 bytes
void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Encrypt (const uint8_t * in, uint8_t * out); // one block
private:
ChipherBlock m_LastBlock;
ECBEncryption m_ECBEncryption;
};
class CBCDecryption
{
public:
CBCDecryption () { memset (m_IV.buf, 0, 16); };
void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy (m_IV.buf, iv, 16); }; // 16 bytes
void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Decrypt (const uint8_t * in, uint8_t * out); // one block
private:
ChipherBlock m_IV;
ECBDecryption m_ECBDecryption;
};
class TunnelEncryption // with double IV encryption
{
public:
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
{
m_LayerEncryption.SetKey (layerKey);
m_IVEncryption.SetKey (ivKey);
}
void Encrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
private:
ECBEncryption m_IVEncryption;
#ifdef AESNI
ECBEncryption m_LayerEncryption;
#else
CBCEncryption m_LayerEncryption;
#endif
};
class TunnelDecryption // with double IV encryption
{
public:
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
{
m_LayerDecryption.SetKey (layerKey);
m_IVDecryption.SetKey (ivKey);
}
void Decrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
private:
ECBDecryption m_IVDecryption;
#ifdef AESNI
ECBDecryption m_LayerDecryption;
#else
CBCDecryption m_LayerDecryption;
#endif
};
void InitCrypto (bool precomputation);
void TerminateCrypto ();
}
}
#endif

View File

@@ -1,269 +0,0 @@
#include <thread>
#include <memory>
#include "Daemon.h"
#include "Config.h"
#include "Log.h"
#include "FS.h"
#include "Base.h"
#include "version.h"
#include "Transports.h"
#include "NTCPSession.h"
#include "RouterInfo.h"
#include "RouterContext.h"
#include "Tunnel.h"
#include "HTTP.h"
#include "NetDb.h"
#include "Garlic.h"
#include "Streaming.h"
#include "Destination.h"
#include "HTTPServer.h"
#include "I2PControl.h"
#include "ClientContext.h"
#include "Crypto.h"
#ifdef USE_UPNP
#include "UPnP.h"
#endif
namespace i2p
{
namespace util
{
class Daemon_Singleton::Daemon_Singleton_Private
{
public:
Daemon_Singleton_Private() {};
~Daemon_Singleton_Private() {};
std::unique_ptr<i2p::http::HTTPServer> httpServer;
std::unique_ptr<i2p::client::I2PControlService> m_I2PControlService;
#ifdef USE_UPNP
i2p::transport::UPnP m_UPnP;
#endif
};
Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {}
Daemon_Singleton::~Daemon_Singleton() {
delete &d;
}
bool Daemon_Singleton::IsService () const
{
bool service = false;
#ifndef _WIN32
i2p::config::GetOption("service", service);
#endif
return service;
}
bool Daemon_Singleton::init(int argc, char* argv[])
{
i2p::config::Init();
i2p::config::ParseCmdline(argc, argv);
std::string config; i2p::config::GetOption("conf", config);
std::string datadir; i2p::config::GetOption("datadir", datadir);
i2p::fs::DetectDataDir(datadir, IsService());
i2p::fs::Init();
datadir = i2p::fs::GetDataDir();
// TODO: drop old name detection in v2.8.0
if (config == "")
{
config = i2p::fs::DataDirPath("i2p.conf");
if (i2p::fs::Exists (config)) {
LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config);
} else {
config = i2p::fs::DataDirPath("i2pd.conf");
if (!i2p::fs::Exists (config)) {
// use i2pd.conf only if exists
config = ""; /* reset */
}
}
}
i2p::config::ParseConfig(config);
i2p::config::Finalize();
i2p::config::GetOption("daemon", isDaemon);
std::string logs = ""; i2p::config::GetOption("log", logs);
std::string logfile = ""; i2p::config::GetOption("logfile", logfile);
std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel);
/* setup logging */
if (isDaemon && (logs == "" || logs == "stdout"))
logs = "file";
i2p::log::Logger().SetLogLevel(loglevel);
if (logs == "file") {
if (logfile == "")
logfile = i2p::fs::DataDirPath("i2pd.log");
LogPrint(eLogInfo, "Log: will send messages to ", logfile);
i2p::log::Logger().SendTo (logfile);
#ifndef _WIN32
} else if (logs == "syslog") {
LogPrint(eLogInfo, "Log: will send messages to syslog");
i2p::log::Logger().SendTo("i2pd", LOG_DAEMON);
#endif
} else {
// use stdout -- default
}
i2p::log::Logger().Ready();
LogPrint(eLogInfo, "i2pd v", VERSION, " starting");
LogPrint(eLogDebug, "FS: main config file: ", config);
LogPrint(eLogDebug, "FS: data directory: ", datadir);
bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation);
i2p::crypto::InitCrypto (precomputation);
i2p::context.Init ();
uint16_t port; i2p::config::GetOption("port", port);
if (!i2p::config::IsDefault("port"))
{
LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port);
i2p::context.UpdatePort (port);
}
std::string host; i2p::config::GetOption("host", host);
if (!i2p::config::IsDefault("host"))
{
LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host);
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));
}
bool ipv6; i2p::config::GetOption("ipv6", ipv6);
bool ipv4; i2p::config::GetOption("ipv4", ipv4);
bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetSupportsV6 (ipv6);
i2p::context.SetSupportsV4 (ipv4);
i2p::context.SetAcceptsTunnels (!transit);
uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
SetMaxNumTransitTunnels (transitTunnels);
bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill);
if (isFloodfill) {
LogPrint(eLogInfo, "Daemon: router will be floodfill");
i2p::context.SetFloodfill (true);
} else {
i2p::context.SetFloodfill (false);
}
/* this section also honors 'floodfill' flag, if set above */
std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth);
if (bandwidth.length () > 0)
{
if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X')
{
i2p::context.SetBandwidth (bandwidth[0]);
LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps");
}
else
{
auto value = std::atoi(bandwidth.c_str());
if (value > 0)
{
i2p::context.SetBandwidth (value);
LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps");
}
else
{
LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'");
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2);
}
}
}
else if (isFloodfill)
{
LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'");
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1);
}
else
{
LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'");
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2);
}
std::string family; i2p::config::GetOption("family", family);
i2p::context.SetFamily (family);
if (family.length () > 0)
LogPrint(eLogInfo, "Daemon: family set to ", family);
return true;
}
bool Daemon_Singleton::start()
{
bool http; i2p::config::GetOption("http.enabled", http);
if (http) {
std::string httpAddr; i2p::config::GetOption("http.address", httpAddr);
uint16_t httpPort; i2p::config::GetOption("http.port", httpPort);
LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort);
d.httpServer = std::unique_ptr<i2p::http::HTTPServer>(new i2p::http::HTTPServer(httpAddr, httpPort));
d.httpServer->Start();
}
LogPrint(eLogInfo, "Daemon: starting NetDB");
i2p::data::netdb.Start();
#ifdef USE_UPNP
LogPrint(eLogInfo, "Daemon: starting UPnP");
d.m_UPnP.Start ();
#endif
LogPrint(eLogInfo, "Daemon: starting Transports");
i2p::transport::transports.Start();
LogPrint(eLogInfo, "Daemon: starting Tunnels");
i2p::tunnel::tunnels.Start();
LogPrint(eLogInfo, "Daemon: starting Client");
i2p::client::context.Start ();
// I2P Control Protocol
bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol);
if (i2pcontrol) {
std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr);
uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort);
LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort);
d.m_I2PControlService = std::unique_ptr<i2p::client::I2PControlService>(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort));
d.m_I2PControlService->Start ();
}
return true;
}
bool Daemon_Singleton::stop()
{
LogPrint(eLogInfo, "Daemon: shutting down");
LogPrint(eLogInfo, "Daemon: stopping Client");
i2p::client::context.Stop();
LogPrint(eLogInfo, "Daemon: stopping Tunnels");
i2p::tunnel::tunnels.Stop();
#ifdef USE_UPNP
LogPrint(eLogInfo, "Daemon: stopping UPnP");
d.m_UPnP.Stop ();
#endif
LogPrint(eLogInfo, "Daemon: stopping Transports");
i2p::transport::transports.Stop();
LogPrint(eLogInfo, "Daemon: stopping NetDB");
i2p::data::netdb.Stop();
if (d.httpServer) {
LogPrint(eLogInfo, "Daemon: stopping HTTP Server");
d.httpServer->Stop();
d.httpServer = nullptr;
}
if (d.m_I2PControlService)
{
LogPrint(eLogInfo, "Daemon: stopping I2PControl");
d.m_I2PControlService->Stop ();
d.m_I2PControlService = nullptr;
}
i2p::crypto::TerminateCrypto ();
return true;
}
}
}

View File

@@ -1,93 +0,0 @@
#ifndef DAEMON_H__
#define DAEMON_H__
#include <memory>
#include <string>
namespace i2p
{
namespace util
{
class Daemon_Singleton_Private;
class Daemon_Singleton
{
public:
virtual bool init(int argc, char* argv[]);
virtual bool start();
virtual bool stop();
virtual void run () {};
bool isDaemon;
bool running;
protected:
Daemon_Singleton();
virtual ~Daemon_Singleton();
bool IsService () const;
// d-pointer for httpServer, httpProxy, etc.
class Daemon_Singleton_Private;
Daemon_Singleton_Private &d;
};
#if defined(QT_GUI_LIB) // check if QT
#define Daemon i2p::util::DaemonQT::Instance()
// dummy, invoked from RunQT
class DaemonQT: public i2p::util::Daemon_Singleton
{
public:
static DaemonQT& Instance()
{
static DaemonQT instance;
return instance;
}
};
#elif defined(_WIN32)
#define Daemon i2p::util::DaemonWin32::Instance()
class DaemonWin32 : public Daemon_Singleton
{
public:
static DaemonWin32& Instance()
{
static DaemonWin32 instance;
return instance;
}
bool init(int argc, char* argv[]);
bool start();
bool stop();
void run ();
};
#else
#define Daemon i2p::util::DaemonLinux::Instance()
class DaemonLinux : public Daemon_Singleton
{
public:
static DaemonLinux& Instance()
{
static DaemonLinux instance;
return instance;
}
bool start();
bool stop();
void run ();
private:
std::string pidfile;
int pidFH;
public:
int gracefullShutdownInterval; // in seconds
};
#endif
}
}
#endif // DAEMON_H__

View File

@@ -1,150 +0,0 @@
#include "Daemon.h"
#ifndef _WIN32
#include <signal.h>
#include <stdlib.h>
#include <thread>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "Config.h"
#include "FS.h"
#include "Log.h"
#include "RouterContext.h"
void handle_signal(int sig)
{
switch (sig)
{
case SIGHUP:
LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log...");
i2p::log::Logger().Reopen ();
break;
case SIGINT:
if (i2p::context.AcceptsTunnels () && !Daemon.gracefullShutdownInterval)
{
i2p::context.SetAcceptsTunnels (false);
Daemon.gracefullShutdownInterval = 10*60; // 10 minutes
LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds");
}
else
Daemon.running = 0;
break;
case SIGABRT:
case SIGTERM:
Daemon.running = 0; // Exit loop
break;
}
}
namespace i2p
{
namespace util
{
bool DaemonLinux::start()
{
if (isDaemon)
{
pid_t pid;
pid = fork();
if (pid > 0) // parent
::exit (EXIT_SUCCESS);
if (pid < 0) // error
{
LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno));
return false;
}
// child
umask(S_IWGRP | S_IRWXO); // 0027
int sid = setsid();
if (sid < 0)
{
LogPrint(eLogError, "Daemon: could not create process group.");
return false;
}
std::string d = i2p::fs::GetDataDir();
if (chdir(d.c_str()) != 0)
{
LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno));
return false;
}
// point std{in,out,err} descriptors to /dev/null
stdin = freopen("/dev/null", "r", stdin);
stdout = freopen("/dev/null", "w", stdout);
stderr = freopen("/dev/null", "w", stderr);
}
// Pidfile
// this code is c-styled and a bit ugly, but we need fd for locking pidfile
std::string pidfile; i2p::config::GetOption("pidfile", pidfile);
if (pidfile == "") {
pidfile = i2p::fs::DataDirPath("i2pd.pid");
}
if (pidfile != "") {
pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600);
if (pidFH < 0)
{
LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno));
return false;
}
if (lockf(pidFH, F_TLOCK, 0) != 0)
{
LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno));
return false;
}
char pid[10];
sprintf(pid, "%d\n", getpid());
ftruncate(pidFH, 0);
if (write(pidFH, pid, strlen(pid)) < 0)
{
LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno));
return false;
}
}
gracefullShutdownInterval = 0; // not specified
// Signal handler
struct sigaction sa;
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGHUP, &sa, 0);
sigaction(SIGABRT, &sa, 0);
sigaction(SIGTERM, &sa, 0);
sigaction(SIGINT, &sa, 0);
return Daemon_Singleton::start();
}
bool DaemonLinux::stop()
{
i2p::fs::Remove(pidfile);
return Daemon_Singleton::stop();
}
void DaemonLinux::run ()
{
while (running)
{
std::this_thread::sleep_for (std::chrono::seconds(1));
if (gracefullShutdownInterval)
{
gracefullShutdownInterval--; // - 1 second
if (gracefullShutdownInterval <= 0)
{
LogPrint(eLogInfo, "Graceful shutdown");
return;
}
}
}
}
}
}
#endif

View File

@@ -1,113 +0,0 @@
#include <thread>
#include "Config.h"
#include "Daemon.h"
#include "util.h"
#include "Log.h"
#ifdef _WIN32
#include "Win32/Win32Service.h"
#ifdef WIN32_APP
#include "Win32/Win32App.h"
#endif
namespace i2p
{
namespace util
{
bool DaemonWin32::init(int argc, char* argv[])
{
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
if (!Daemon_Singleton::init(argc, argv))
return false;
std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl);
if (serviceControl == "install")
{
LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service");
InstallService(
SERVICE_NAME, // Name of service
SERVICE_DISPLAY_NAME, // Name to display
SERVICE_START_TYPE, // Service start type
SERVICE_DEPENDENCIES, // Dependencies
SERVICE_ACCOUNT, // Service running account
SERVICE_PASSWORD // Password of the account
);
return false;
}
else if (serviceControl == "remove")
{
LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service");
UninstallService(SERVICE_NAME);
return false;
}
if (isDaemon)
{
LogPrint(eLogDebug, "Daemon: running as service");
I2PService service(SERVICE_NAME);
if (!I2PService::Run(service))
{
LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError());
return false;
}
return false;
}
else
LogPrint(eLogDebug, "Daemon: running as user");
return true;
}
bool DaemonWin32::start()
{
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
#ifdef WIN32_APP
if (!i2p::win32::StartWin32App ()) return false;
// override log
i2p::config::SetOption("log", std::string ("file"));
#endif
bool ret = Daemon_Singleton::start();
if (ret && i2p::log::Logger().GetLogType() == eLogFile)
{
// TODO: find out where this garbage to console comes from
SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
}
bool insomnia; i2p::config::GetOption("insomnia", insomnia);
if (insomnia)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
return ret;
}
bool DaemonWin32::stop()
{
#ifdef WIN32_APP
i2p::win32::StopWin32App ();
#endif
return Daemon_Singleton::stop();
}
void DaemonWin32::run ()
{
#ifdef WIN32_APP
i2p::win32::RunWin32App ();
#else
while (running)
{
std::this_thread::sleep_for (std::chrono::seconds(1));
}
#endif
}
}
}
#endif

View File

@@ -1,143 +0,0 @@
#include <string.h>
#include <vector>
#include "Crypto.h"
#include "Log.h"
#include "TunnelBase.h"
#include "RouterContext.h"
#include "Destination.h"
#include "Datagram.h"
namespace i2p
{
namespace datagram
{
DatagramDestination::DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner):
m_Owner (owner), m_Receiver (nullptr)
{
}
DatagramDestination::~DatagramDestination ()
{
}
void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort)
{
uint8_t buf[MAX_DATAGRAM_SIZE];
auto identityLen = m_Owner->GetIdentity ()->ToBuffer (buf, MAX_DATAGRAM_SIZE);
uint8_t * signature = buf + identityLen;
auto signatureLen = m_Owner->GetIdentity ()->GetSignatureLen ();
uint8_t * buf1 = signature + signatureLen;
size_t headerLen = identityLen + signatureLen;
memcpy (buf1, payload, len);
if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
uint8_t hash[32];
SHA256(buf1, len, hash);
m_Owner->Sign (hash, 32, signature);
}
else
m_Owner->Sign (buf1, len, signature);
auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort);
auto remote = m_Owner->FindLeaseSet (ident);
if (remote)
m_Owner->GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote));
else
m_Owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg));
}
void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> remote, std::shared_ptr<I2NPMessage> msg)
{
if (remote)
SendMsg (msg, remote);
}
void DatagramDestination::SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote)
{
auto outboundTunnel = m_Owner->GetTunnelPool ()->GetNextOutboundTunnel ();
auto leases = remote->GetNonExpiredLeases ();
if (!leases.empty () && outboundTunnel)
{
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
uint32_t i = rand () % leases.size ();
auto garlic = m_Owner->WrapMessage (remote, msg, true);
msgs.push_back (i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeTunnel,
leases[i]->tunnelGateway, leases[i]->tunnelID,
garlic
});
outboundTunnel->SendTunnelDataMsg (msgs);
}
else
{
if (outboundTunnel)
LogPrint (eLogWarning, "Failed to send datagram. All leases expired");
else
LogPrint (eLogWarning, "Failed to send datagram. No outbound tunnels");
}
}
void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
i2p::data::IdentityEx identity;
size_t identityLen = identity.FromBuffer (buf, len);
const uint8_t * signature = buf + identityLen;
size_t headerLen = identityLen + identity.GetSignatureLen ();
bool verified = false;
if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
uint8_t hash[32];
SHA256(buf + headerLen, len - headerLen, hash);
verified = identity.Verify (hash, 32, signature);
}
else
verified = identity.Verify (buf + headerLen, len - headerLen, signature);
if (verified)
{
auto it = m_ReceiversByPorts.find (toPort);
if (it != m_ReceiversByPorts.end ())
it->second (identity, fromPort, toPort, buf + headerLen, len -headerLen);
else if (m_Receiver != nullptr)
m_Receiver (identity, fromPort, toPort, buf + headerLen, len -headerLen);
else
LogPrint (eLogWarning, "Receiver for datagram is not set");
}
else
LogPrint (eLogWarning, "Datagram signature verification failed");
}
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
// unzip it
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE);
if (uncompressedLen)
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
}
std::shared_ptr<I2NPMessage> DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
{
auto msg = NewI2NPMessage ();
uint8_t * buf = msg->GetPayload ();
buf += 4; // reserve for length
size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len);
if (size)
{
htobe32buf (msg->GetPayload (), size); // length
htobe16buf (buf + 4, fromPort); // source port
htobe16buf (buf + 6, toPort); // destination port
buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
msg->len += size + 4;
msg->FillI2NPMessageHeader (eI2NPData);
}
else
msg = nullptr;
return msg;
}
}
}

View File

@@ -1,61 +0,0 @@
#ifndef DATAGRAM_H__
#define DATAGRAM_H__
#include <inttypes.h>
#include <memory>
#include <functional>
#include <map>
#include "Base.h"
#include "Identity.h"
#include "LeaseSet.h"
#include "I2NPProtocol.h"
namespace i2p
{
namespace client
{
class ClientDestination;
}
namespace datagram
{
const size_t MAX_DATAGRAM_SIZE = 32768;
class DatagramDestination
{
typedef std::function<void (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> Receiver;
public:
DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner);
~DatagramDestination ();
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0);
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
void ResetReceiver () { m_Receiver = nullptr; };
void SetReceiver (const Receiver& receiver, uint16_t port) { m_ReceiversByPorts[port] = receiver; };
void ResetReceiver (uint16_t port) { m_ReceiversByPorts.erase (port); };
private:
void HandleLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<I2NPMessage> msg);
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
void SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
private:
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
Receiver m_Receiver; // default
std::map<uint16_t, Receiver> m_ReceiversByPorts;
i2p::data::GzipInflator m_Inflator;
i2p::data::GzipDeflator m_Deflator;
};
}
}
#endif

View File

@@ -1,854 +0,0 @@
#include <algorithm>
#include <cassert>
#include <boost/lexical_cast.hpp>
#include "Crypto.h"
#include "Log.h"
#include "FS.h"
#include "Timestamp.h"
#include "NetDb.h"
#include "Destination.h"
#include "util.h"
namespace i2p
{
namespace client
{
LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map<std::string, std::string> * params):
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_IsPublic (isPublic),
m_PublishReplyToken (0), m_PublishConfirmationTimer (m_Service),
m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service)
{
int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH;
int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH;
int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY;
int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY;
int numTags = DEFAULT_TAGS_TO_SEND;
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
if (params)
{
auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH);
if (it != params->end ())
{
int len = i2p::util::lexical_cast<int>(it->second, inboundTunnelLen);
if (len > 0)
{
inboundTunnelLen = len;
}
LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", inboundTunnelLen);
}
it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH);
if (it != params->end ())
{
int len = i2p::util::lexical_cast<int>(it->second, outboundTunnelLen);
if (len > 0)
{
outboundTunnelLen = len;
}
LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", outboundTunnelLen);
}
it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY);
if (it != params->end ())
{
int quantity = i2p::util::lexical_cast<int>(it->second, inboundTunnelsQuantity);
if (quantity > 0)
{
inboundTunnelsQuantity = quantity;
LogPrint (eLogInfo, "Destination: Inbound tunnels quantity set to ", quantity);
}
}
it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY);
if (it != params->end ())
{
int quantity = i2p::util::lexical_cast<int>(it->second, outboundTunnelsQuantity);
if (quantity > 0)
{
outboundTunnelsQuantity = quantity;
LogPrint (eLogInfo, "Destination: Outbound tunnels quantity set to ", quantity);
}
}
it = params->find (I2CP_PARAM_TAGS_TO_SEND);
if (it != params->end ())
{
int tagsToSend = i2p::util::lexical_cast<int>(it->second, numTags);
if (tagsToSend > 0)
{
numTags = tagsToSend;
LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend);
}
}
it = params->find (I2CP_PARAM_EXPLICIT_PEERS);
if (it != params->end ())
{
explicitPeers = std::make_shared<std::vector<i2p::data::IdentHash> >();
std::stringstream ss(it->second);
std::string b64;
while (std::getline (ss, b64, ','))
{
i2p::data::IdentHash ident;
ident.FromBase64 (b64);
explicitPeers->push_back (ident);
}
LogPrint (eLogInfo, "Destination: Explicit peers set to ", it->second);
}
}
SetNumTags (numTags);
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity);
if (explicitPeers)
m_Pool->SetExplicitPeers (explicitPeers);
}
LeaseSetDestination::~LeaseSetDestination ()
{
if (m_IsRunning)
Stop ();
for (auto it: m_LeaseSetRequests)
if (it.second->requestComplete) it.second->requestComplete (nullptr);
m_LeaseSetRequests.clear ();
if (m_Pool)
i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool);
}
void LeaseSetDestination::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Destination: runtime exception: ", ex.what ());
}
}
}
bool LeaseSetDestination::Start ()
{
if (!m_IsRunning)
{
m_IsRunning = true;
m_Pool->SetLocalDestination (shared_from_this ());
m_Pool->SetActive (true);
m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ()));
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
return true;
}
else
return false;
}
bool LeaseSetDestination::Stop ()
{
if (m_IsRunning)
{
m_CleanupTimer.cancel ();
m_PublishConfirmationTimer.cancel ();
m_PublishVerificationTimer.cancel ();
m_IsRunning = false;
if (m_Pool)
{
m_Pool->SetLocalDestination (nullptr);
i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
}
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
}
return true;
}
else
return false;
}
std::shared_ptr<const i2p::data::LeaseSet> LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
{
auto it = m_RemoteLeaseSets.find (ident);
if (it != m_RemoteLeaseSets.end ())
{
if (!it->second->IsExpired ())
return it->second;
else
LogPrint (eLogWarning, "Destination: remote LeaseSet expired");
}
else
{
auto ls = i2p::data::netdb.FindLeaseSet (ident);
if (ls && !ls->IsExpired ())
{
ls->PopulateLeases (); // since we don't store them in netdb
m_RemoteLeaseSets[ident] = ls;
return ls;
}
}
return nullptr;
}
std::shared_ptr<const i2p::data::LocalLeaseSet> LeaseSetDestination::GetLeaseSet ()
{
if (!m_Pool) return nullptr;
if (!m_LeaseSet)
UpdateLeaseSet ();
return m_LeaseSet;
}
void LeaseSetDestination::SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet)
{
m_LeaseSet.reset (newLeaseSet);
if (m_IsPublic)
{
m_PublishVerificationTimer.cancel ();
Publish ();
}
}
void LeaseSetDestination::UpdateLeaseSet ()
{
int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels
if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum
CreateNewLeaseSet (m_Pool->GetInboundTunnels (numTunnels));
}
bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
{
struct
{
uint8_t k[32], t[32];
} data;
memcpy (data.k, key, 32);
memcpy (data.t, tag, 32);
auto s = shared_from_this ();
m_Service.post ([s,data](void)
{
s->AddSessionKey (data.k, data.t);
});
return true;
}
void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg));
}
void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msg));
}
void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET];
switch (typeID)
{
case eI2NPData:
HandleDataMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
break;
case eI2NPDeliveryStatus:
// we assume tunnel tests non-encrypted
HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
break;
case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
break;
case eI2NPDatabaseSearchReply:
HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
break;
default:
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
}
}
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
{
uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET);
size_t offset = DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore");
offset += 36;
}
std::shared_ptr<i2p::data::LeaseSet> leaseSet;
if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet
{
LogPrint (eLogDebug, "Remote LeaseSet");
auto it = m_RemoteLeaseSets.find (buf + DATABASE_STORE_KEY_OFFSET);
if (it != m_RemoteLeaseSets.end ())
{
leaseSet = it->second;
if (leaseSet->IsNewer (buf + offset, len - offset))
{
leaseSet->Update (buf + offset, len - offset);
if (leaseSet->IsValid ())
LogPrint (eLogDebug, "Remote LeaseSet updated");
else
{
LogPrint (eLogDebug, "Remote LeaseSet update failed");
m_RemoteLeaseSets.erase (it);
leaseSet = nullptr;
}
}
else
LogPrint (eLogDebug, "Remote LeaseSet is older. Not updated");
}
else
{
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset);
if (leaseSet->IsValid ())
{
if (leaseSet->GetIdentHash () != GetIdentHash ())
{
LogPrint (eLogDebug, "New remote LeaseSet added");
m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet;
}
else
LogPrint (eLogDebug, "Own remote LeaseSet dropped");
}
else
{
LogPrint (eLogError, "New remote LeaseSet failed");
leaseSet = nullptr;
}
}
}
else
LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped");
auto it1 = m_LeaseSetRequests.find (buf + DATABASE_STORE_KEY_OFFSET);
if (it1 != m_LeaseSetRequests.end ())
{
it1->second->requestTimeoutTimer.cancel ();
if (it1->second->requestComplete) it1->second->requestComplete (leaseSet);
m_LeaseSetRequests.erase (it1);
}
}
void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len)
{
i2p::data::IdentHash key (buf);
int num = buf[32]; // num
LogPrint (eLogDebug, "Destination: DatabaseSearchReply for ", key.ToBase64 (), " num=", num);
auto it = m_LeaseSetRequests.find (key);
if (it != m_LeaseSetRequests.end ())
{
auto request = it->second;
bool found = false;
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
{
for (int i = 0; i < num; i++)
{
i2p::data::IdentHash peerHash (buf + 33 + i*32);
auto floodfill = i2p::data::netdb.FindRouter (peerHash);
if (floodfill)
{
LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", peerHash.ToBase64 ());
if (SendLeaseSetRequest (key, floodfill, request))
found = true;
}
else
{
LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message
i2p::data::netdb.RequestDestination (peerHash);
}
}
if (!found)
LogPrint (eLogError, "Destination: Suggested floodfills are not presented in netDb");
}
else
LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
if (!found)
{
if (request->requestComplete) request->requestComplete (nullptr);
m_LeaseSetRequests.erase (key);
}
}
else
LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
}
void LeaseSetDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
if (msgID == m_PublishReplyToken)
{
LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed");
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 0;
// schedule verification
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1));
}
else
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg);
}
void LeaseSetDestination::SetLeaseSetUpdated ()
{
i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
UpdateLeaseSet ();
}
void LeaseSetDestination::Publish ()
{
if (!m_LeaseSet || !m_Pool)
{
LogPrint (eLogError, "Destination: Can't publish non-existing LeaseSet");
return;
}
if (m_PublishReplyToken)
{
LogPrint (eLogDebug, "Destination: Publishing LeaseSet is pending");
return;
}
auto outbound = m_Pool->GetNextOutboundTunnel ();
if (!outbound)
{
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
return;
}
auto inbound = m_Pool->GetNextInboundTunnel ();
if (!inbound)
{
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
return;
}
auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills);
if (!floodfill)
{
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
m_ExcludedFloodfills.clear ();
return;
}
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4);
auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken, inbound));
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg);
}
void LeaseSetDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (m_PublishReplyToken)
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again");
m_PublishReplyToken = 0;
Publish ();
}
}
}
void LeaseSetDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto s = shared_from_this ();
RequestLeaseSet (GetIdentHash (),
// "this" added due to bug in gcc 4.7-4.8
[s,this](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
if (leaseSet)
{
if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet)
{
// we got latest LeasetSet
LogPrint (eLogDebug, "Destination: published LeaseSet verified");
s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL));
s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1));
return;
}
}
else
LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet");
// we have to publish again
s->Publish ();
});
}
}
bool LeaseSetDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
{
if (!m_Pool || !IsReady ())
{
if (requestComplete) requestComplete (nullptr);
return false;
}
m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete));
return true;
}
void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest)
{
auto s = shared_from_this ();
m_Service.post ([dest, s](void)
{
auto it = s->m_LeaseSetRequests.find (dest);
if (it != s->m_LeaseSetRequests.end ())
{
auto requestComplete = it->second->requestComplete;
s->m_LeaseSetRequests.erase (it);
if (requestComplete) requestComplete (nullptr);
}
});
}
void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
{
std::set<i2p::data::IdentHash> excluded;
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
if (floodfill)
{
auto request = std::make_shared<LeaseSetRequest> (m_Service);
request->requestComplete = requestComplete;
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
if (ret.second) // inserted
{
if (!SendLeaseSetRequest (dest, floodfill, request))
{
// request failed
m_LeaseSetRequests.erase (dest);
if (request->requestComplete) request->requestComplete (nullptr);
}
}
else // duplicate
{
LogPrint (eLogWarning, "Destination: Request of LeaseSet ", dest.ToBase64 (), " is pending already");
// TODO: queue up requests
if (request->requestComplete) request->requestComplete (nullptr);
}
}
else
{
LogPrint (eLogError, "Destination: Can't request LeaseSet, no floodfills found");
if (requestComplete) requestComplete (nullptr);
}
}
bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest,
std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request)
{
if (!request->replyTunnel || !request->replyTunnel->IsEstablished ())
request->replyTunnel = m_Pool->GetNextInboundTunnel ();
if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found");
if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ())
request->outboundTunnel = m_Pool->GetNextOutboundTunnel ();
if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found");
if (request->replyTunnel && request->outboundTunnel)
{
request->excluded.insert (nextFloodfill->GetIdentHash ());
request->requestTime = i2p::util::GetSecondsSinceEpoch ();
request->requestTimeoutTimer.cancel ();
uint8_t replyKey[32], replyTag[32];
RAND_bytes (replyKey, 32); // random session key
RAND_bytes (replyTag, 32); // random session tag
AddSessionKey (replyKey, replyTag);
auto msg = WrapMessage (nextFloodfill,
CreateLeaseSetDatabaseLookupMsg (dest, request->excluded,
request->replyTunnel, replyKey, replyTag));
request->outboundTunnel->SendTunnelDataMsg (
{
i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeRouter,
nextFloodfill->GetIdentHash (), 0, msg
}
});
request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer,
shared_from_this (), std::placeholders::_1, dest));
}
else
return false;
return true;
}
void LeaseSetDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto it = m_LeaseSetRequests.find (dest);
if (it != m_LeaseSetRequests.end ())
{
bool done = false;
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
if (floodfill)
{
// reset tunnels, because one them might fail
it->second->outboundTunnel = nullptr;
it->second->replyTunnel = nullptr;
done = !SendLeaseSetRequest (dest, floodfill, it->second);
}
else
done = true;
}
else
{
LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds");
done = true;
}
if (done)
{
auto requestComplete = it->second->requestComplete;
m_LeaseSetRequests.erase (it);
if (requestComplete) requestComplete (nullptr);
}
}
}
}
void LeaseSetDestination::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
CleanupExpiredTags ();
CleanupRemoteLeaseSets ();
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
}
void LeaseSetDestination::CleanupRemoteLeaseSets ()
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();)
{
if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
{
LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
it = m_RemoteLeaseSets.erase (it);
}
else
it++;
}
}
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params):
LeaseSetDestination (isPublic, params),
m_Keys (keys), m_DatagramDestination (nullptr)
{
if (isPublic)
PersistTemporaryKeys ();
else
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
if (isPublic)
LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created");
}
ClientDestination::~ClientDestination ()
{
if (m_DatagramDestination)
delete m_DatagramDestination;
}
bool ClientDestination::Start ()
{
if (LeaseSetDestination::Start ())
{
m_StreamingDestination = std::make_shared<i2p::stream::StreamingDestination> (GetSharedFromThis ()); // TODO:
m_StreamingDestination->Start ();
for (auto it: m_StreamingDestinationsByPorts)
it.second->Start ();
return true;
}
else
return false;
}
bool ClientDestination::Stop ()
{
if (LeaseSetDestination::Stop ())
{
m_StreamingDestination->Stop ();
m_StreamingDestination = nullptr;
for (auto it: m_StreamingDestinationsByPorts)
it.second->Stop ();
if (m_DatagramDestination)
{
auto d = m_DatagramDestination;
m_DatagramDestination = nullptr;
delete d;
}
return true;
}
else
return false;
}
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len)
{
uint32_t length = bufbe32toh (buf);
buf += 4;
// we assume I2CP payload
uint16_t fromPort = bufbe16toh (buf + 4), // source
toPort = bufbe16toh (buf + 6); // destination
switch (buf[9])
{
case PROTOCOL_TYPE_STREAMING:
{
// streaming protocol
auto dest = GetStreamingDestination (toPort);
if (dest)
dest->HandleDataMessagePayload (buf, length);
else
LogPrint (eLogError, "Destination: Missing streaming destination");
}
break;
case PROTOCOL_TYPE_DATAGRAM:
// datagram protocol
if (m_DatagramDestination)
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length);
else
LogPrint (eLogError, "Destination: Missing datagram destination");
break;
default:
LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]);
}
}
void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port)
{
if (!streamRequestComplete)
{
LogPrint (eLogError, "Destination: request callback is not specified in CreateStream");
return;
}
auto leaseSet = FindLeaseSet (dest);
if (leaseSet)
streamRequestComplete(CreateStream (leaseSet, port));
else
{
auto s = GetSharedFromThis ();
RequestDestination (dest,
[s, streamRequestComplete, port](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls)
streamRequestComplete(s->CreateStream (ls, port));
else
streamRequestComplete (nullptr);
});
}
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
{
if (m_StreamingDestination)
return m_StreamingDestination->CreateNewOutgoingStream (remote, port);
else
return nullptr;
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::GetStreamingDestination (int port) const
{
if (port)
{
auto it = m_StreamingDestinationsByPorts.find (port);
if (it != m_StreamingDestinationsByPorts.end ())
return it->second;
}
// if port is zero or not found, use default destination
return m_StreamingDestination;
}
void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor)
{
if (m_StreamingDestination)
m_StreamingDestination->SetAcceptor (acceptor);
}
void ClientDestination::StopAcceptingStreams ()
{
if (m_StreamingDestination)
m_StreamingDestination->ResetAcceptor ();
}
bool ClientDestination::IsAcceptingStreams () const
{
if (m_StreamingDestination)
return m_StreamingDestination->IsAcceptorSet ();
return false;
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (int port, bool gzip)
{
auto dest = std::make_shared<i2p::stream::StreamingDestination> (GetSharedFromThis (), port, gzip);
if (port)
m_StreamingDestinationsByPorts[port] = dest;
else // update default
m_StreamingDestination = dest;
return dest;
}
i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination ()
{
if (!m_DatagramDestination)
m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ());
return m_DatagramDestination;
}
std::vector<std::shared_ptr<const i2p::stream::Stream> > ClientDestination::GetAllStreams () const
{
std::vector<std::shared_ptr<const i2p::stream::Stream> > ret;
if (m_StreamingDestination)
{
for (auto& it: m_StreamingDestination->GetStreams ())
ret.push_back (it.second);
}
for (auto& it: m_StreamingDestinationsByPorts)
for (auto& it1: it.second->GetStreams ())
ret.push_back (it1.second);
return ret;
}
void ClientDestination::PersistTemporaryKeys ()
{
std::string ident = GetIdentHash().ToBase32();
std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat"));
std::ifstream f(path, std::ifstream::binary);
if (f) {
f.read ((char *)m_EncryptionPublicKey, 256);
f.read ((char *)m_EncryptionPrivateKey, 256);
return;
}
LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p");
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
if (f1) {
f1.write ((char *)m_EncryptionPublicKey, 256);
f1.write ((char *)m_EncryptionPrivateKey, 256);
return;
}
LogPrint(eLogError, "Destinations: Can't save keys to ", path);
}
void ClientDestination::CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
{
auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), m_EncryptionPublicKey, tunnels);
// sign
Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO
SetLeaseSet (leaseSet);
}
}
}

View File

@@ -1,202 +0,0 @@
#ifndef DESTINATION_H__
#define DESTINATION_H__
#include <thread>
#include <mutex>
#include <memory>
#include <map>
#include <set>
#include <string>
#include <functional>
#include <boost/asio.hpp>
#include "Identity.h"
#include "TunnelPool.h"
#include "Crypto.h"
#include "LeaseSet.h"
#include "Garlic.h"
#include "NetDb.h"
#include "Streaming.h"
#include "Datagram.h"
namespace i2p
{
namespace client
{
const uint8_t PROTOCOL_TYPE_STREAMING = 6;
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
const uint8_t PROTOCOL_TYPE_RAW = 18;
const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successfull publish
const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes
const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
// I2CP
const char I2CP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length";
const int DEFAULT_INBOUND_TUNNEL_LENGTH = 3;
const char I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length";
const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3;
const char I2CP_PARAM_INBOUND_TUNNELS_QUANTITY[] = "inbound.quantity";
const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5;
const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity";
const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5;
const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers";
const int STREAM_REQUEST_TIMEOUT = 60; //in seconds
const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend";
const int DEFAULT_TAGS_TO_SEND = 40;
typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
class LeaseSetDestination: public i2p::garlic::GarlicDestination,
public std::enable_shared_from_this<LeaseSetDestination>
{
typedef std::function<void (std::shared_ptr<i2p::data::LeaseSet> leaseSet)> RequestComplete;
// leaseSet = nullptr means not found
struct LeaseSetRequest
{
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
std::set<i2p::data::IdentHash> excluded;
uint64_t requestTime;
boost::asio::deadline_timer requestTimeoutTimer;
RequestComplete requestComplete;
std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
std::shared_ptr<i2p::tunnel::InboundTunnel> replyTunnel;
};
public:
LeaseSetDestination (bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~LeaseSetDestination ();
virtual bool Start ();
virtual bool Stop ();
bool IsRunning () const { return m_IsRunning; };
boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; };
std::shared_ptr<const i2p::data::LeaseSet> FindLeaseSet (const i2p::data::IdentHash& ident);
bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr);
void CancelDestinationRequest (const i2p::data::IdentHash& dest);
// implements GarlicDestination
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet ();
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
// override GarlicDestination
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void SetLeaseSetUpdated ();
protected:
void SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet);
// I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
virtual void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels) = 0;
private:
void Run ();
void UpdateLeaseSet ();
void Publish ();
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
void HandlePublishVerificationTimer (const boost::system::error_code& ecode);
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete);
bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request);
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
void HandleCleanupTimer (const boost::system::error_code& ecode);
void CleanupRemoteLeaseSets ();
private:
volatile bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
std::map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
std::map<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
std::shared_ptr<i2p::data::LocalLeaseSet> m_LeaseSet;
bool m_IsPublic;
uint32_t m_PublishReplyToken;
std::set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_CleanupTimer;
public:
// for HTTP only
int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); };
};
class ClientDestination: public LeaseSetDestination
{
public:
ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination ();
bool Start ();
bool Stop ();
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
// streaming
std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
// following methods operate with default streaming destination
void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0);
std::shared_ptr<i2p::stream::Stream> CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor);
void StopAcceptingStreams ();
bool IsAcceptingStreams () const;
// datagram
i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; };
i2p::datagram::DatagramDestination * CreateDatagramDestination ();
// implements LocalDestination
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
protected:
// I2CP
void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
private:
std::shared_ptr<ClientDestination> GetSharedFromThis ()
{ return std::static_pointer_cast<ClientDestination>(shared_from_this ()); }
void PersistTemporaryKeys ();
private:
i2p::data::PrivateKeys m_Keys;
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
i2p::datagram::DatagramDestination * m_DatagramDestination;
public:
// for HTTP only
std::vector<std::shared_ptr<const i2p::stream::Stream> > GetAllStreams () const;
};
}
}
#endif

173
FS.cpp
View File

@@ -1,173 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <algorithm>
#include <boost/filesystem.hpp>
#ifdef _WIN32
#include <shlobj.h>
#endif
#include "Base.h"
#include "FS.h"
#include "Log.h"
namespace i2p {
namespace fs {
std::string appName = "i2pd";
std::string dataDir = "";
#ifdef _WIN32
std::string dirSep = "\\";
#else
std::string dirSep = "/";
#endif
const std::string & GetAppName () {
return appName;
}
void SetAppName (const std::string& name) {
appName = name;
}
const std::string & GetDataDir () {
return dataDir;
}
void DetectDataDir(const std::string & cmdline_param, bool isService) {
if (cmdline_param != "") {
dataDir = cmdline_param;
return;
}
#if defined(WIN32) || defined(_WIN32)
char localAppData[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
dataDir = std::string(localAppData) + "\\" + appName;
return;
#elif defined(MAC_OSX)
char *home = getenv("HOME");
dataDir = (home != NULL && strlen(home) > 0) ? home : "";
dataDir += "/Library/Application Support/" + appName;
return;
#else /* other unix */
#if defined(ANDROID)
if (boost::filesystem::exists("/sdcard"))
{
dataDir = "/sdcard/" + appName;
return;
}
// otherwise use /data/files
#endif
char *home = getenv("HOME");
if (isService) {
dataDir = "/var/lib/" + appName;
} else if (home != NULL && strlen(home) > 0) {
dataDir = std::string(home) + "/." + appName;
} else {
dataDir = "/tmp/" + appName;
}
return;
#endif
}
bool Init() {
if (!boost::filesystem::exists(dataDir))
boost::filesystem::create_directory(dataDir);
std::string destinations = DataDirPath("destinations");
if (!boost::filesystem::exists(destinations))
boost::filesystem::create_directory(destinations);
return true;
}
bool ReadDir(const std::string & path, std::vector<std::string> & files) {
if (!boost::filesystem::exists(path))
return false;
boost::filesystem::directory_iterator it(path);
boost::filesystem::directory_iterator end;
for ( ; it != end; it++) {
if (!boost::filesystem::is_regular_file(it->status()))
continue;
files.push_back(it->path().string());
}
return true;
}
bool Exists(const std::string & path) {
return boost::filesystem::exists(path);
}
bool Remove(const std::string & path) {
if (!boost::filesystem::exists(path))
return false;
return boost::filesystem::remove(path);
}
bool CreateDirectory (const std::string& path)
{
if (boost::filesystem::exists(path) &&
boost::filesystem::is_directory (boost::filesystem::status (path))) return true;
return boost::filesystem::create_directory(path);
}
void HashedStorage::SetPlace(const std::string &path) {
root = path + i2p::fs::dirSep + name;
}
bool HashedStorage::Init(const char * chars, size_t count) {
if (!boost::filesystem::exists(root)) {
boost::filesystem::create_directories(root);
}
for (size_t i = 0; i < count; i++) {
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
if (boost::filesystem::exists(p))
continue;
if (boost::filesystem::create_directory(p))
continue; /* ^ throws exception on failure */
return false;
}
return true;
}
std::string HashedStorage::Path(const std::string & ident) const {
std::string safe_ident = ident;
std::replace(safe_ident.begin(), safe_ident.end(), '/', '-');
std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-');
std::stringstream t("");
t << this->root << i2p::fs::dirSep;
t << prefix1 << safe_ident[0] << i2p::fs::dirSep;
t << prefix2 << safe_ident << "." << suffix;
return t.str();
}
void HashedStorage::Remove(const std::string & ident) {
std::string path = Path(ident);
if (!boost::filesystem::exists(path))
return;
boost::filesystem::remove(path);
}
void HashedStorage::Traverse(std::vector<std::string> & files) {
boost::filesystem::path p(root);
boost::filesystem::recursive_directory_iterator it(p);
boost::filesystem::recursive_directory_iterator end;
for ( ; it != end; it++) {
if (!boost::filesystem::is_regular_file( it->status() ))
continue;
const std::string & t = it->path().string();
files.push_back(t);
}
}
} // fs
} // i2p

155
FS.h
View File

@@ -1,155 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef FS_H__
#define FS_H__
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
namespace i2p {
namespace fs {
extern std::string dirSep;
/**
* @brief Class to work with NetDb & Router profiles
*
* Usage:
*
* const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
* auto h = HashedStorage("name", "y", "z-", ".txt");
* h.SetPlace("/tmp/hs-test");
* h.GetName() -> gives "name"
* h.GetRoot() -> gives "/tmp/hs-test/name"
* h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet
* h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt
* h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists
* std::vector<std::string> files;
* h.Traverse(files); <- finds all files in storage and saves in given vector
*/
class HashedStorage {
protected:
std::string root; /**< path to storage with it's name included */
std::string name; /**< name of the storage */
std::string prefix1; /**< hashed directory prefix */
std::string prefix2; /**< prefix of file in storage */
std::string suffix; /**< suffix of file in storage (extension) */
public:
HashedStorage(const char *n, const char *p1, const char *p2, const char *s):
name(n), prefix1(p1), prefix2(p2), suffix(s) {};
/** create subdirs in storage */
bool Init(const char* chars, size_t cnt);
const std::string & GetRoot() const { return root; }
const std::string & GetName() const { return name; }
/** set directory where to place storage directory */
void SetPlace(const std::string & path);
/** path to file with given ident */
std::string Path(const std::string & ident) const;
/** remove file by ident */
void Remove(const std::string & ident);
/** find all files in storage and store list in provided vector */
void Traverse(std::vector<std::string> & files);
};
/** @brief Returns current application name, default 'i2pd' */
const std::string & GetAppName ();
/** @brief Set applicaton name, affects autodetection of datadir */
void SetAppName (const std::string& name);
/** @brief Returns datadir path */
const std::string & GetDataDir();
/**
* @brief Set datadir either from cmdline option or using autodetection
* @param cmdline_param Value of cmdline parameter --datadir=<something>
* @param isService Value of cmdline parameter --service
*
* Examples of autodetected paths:
*
* Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\
* Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\
* Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/
* Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/
*/
void DetectDataDir(const std::string & cmdline_datadir, bool isService = false);
/**
* @brief Create subdirectories inside datadir
*/
bool Init();
/**
* @brief Get list of files in directory
* @param path Path to directory
* @param files Vector to store found files
* @return true on success and false if directory not exists
*/
bool ReadDir(const std::string & path, std::vector<std::string> & files);
/**
* @brief Remove file with given path
* @param path Absolute path to file
* @return true on success, false if file not exists, throws exception on error
*/
bool Remove(const std::string & path);
/**
* @brief Check existence of file
* @param path Absolute path to file
* @return true if file exists, false otherwise
*/
bool Exists(const std::string & path);
bool CreateDirectory (const std::string& path);
template<typename T>
void _ExpandPath(std::stringstream & path, T c) {
path << i2p::fs::dirSep << c;
}
template<typename T, typename ... Other>
void _ExpandPath(std::stringstream & path, T c, Other ... other) {
_ExpandPath(path, c);
_ExpandPath(path, other ...);
}
/**
* @brief Get path relative to datadir
*
* Examples (with datadir = "/tmp/i2pd"):
*
* i2p::fs::Path("test") -> '/tmp/i2pd/test'
* i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt'
*/
template<typename ... Other>
std::string DataDirPath(Other ... components) {
std::stringstream s("");
s << i2p::fs::GetDataDir();
_ExpandPath(s, components ...);
return s.str();
}
template<typename Storage, typename... Filename>
std::string StorageRootPath (const Storage& storage, Filename... filenames)
{
std::stringstream s("");
s << storage.GetRoot ();
_ExpandPath(s, filenames...);
return s.str();
}
} // fs
} // i2p
#endif // /* FS_H__ */

View File

@@ -1,175 +0,0 @@
#include <string.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include "Crypto.h"
#include "FS.h"
#include "Log.h"
#include "Family.h"
namespace i2p
{
namespace data
{
Families::Families ()
{
}
Families::~Families ()
{
}
void Families::LoadCertificate (const std::string& filename)
{
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
if (ret)
{
SSL * ssl = SSL_new (ctx);
X509 * cert = SSL_get_certificate (ssl);
if (cert)
{
std::shared_ptr<i2p::crypto::Verifier> verifier;
// extract issuer name
char name[100];
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
char * cn = strstr (name, "CN=");
if (cn)
{
cn += 3;
char * family = strstr (cn, ".family");
if (family) family[0] = 0;
}
auto pkey = X509_get_pubkey (cert);
int keyType = EVP_PKEY_type(pkey->type);
switch (keyType)
{
case EVP_PKEY_DSA:
// TODO:
break;
case EVP_PKEY_EC:
{
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
if (ecKey)
{
auto group = EC_KEY_get0_group (ecKey);
if (group)
{
int curve = EC_GROUP_get_curve_name (group);
if (curve == NID_X9_62_prime256v1)
{
uint8_t signingKey[64];
BIGNUM * x = BN_new(), * y = BN_new();
EC_POINT_get_affine_coordinates_GFp (group,
EC_KEY_get0_public_key (ecKey), x, y, NULL);
i2p::crypto::bn2buf (x, signingKey, 32);
i2p::crypto::bn2buf (y, signingKey + 32, 32);
BN_free (x); BN_free (y);
verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>(signingKey);
}
else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
}
EC_KEY_free (ecKey);
}
break;
}
default:
LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
}
EVP_PKEY_free (pkey);
if (verifier && cn)
m_SigningKeys[cn] = verifier;
}
SSL_free (ssl);
}
else
LogPrint (eLogError, "Family: Can't open certificate file ", filename);
SSL_CTX_free (ctx);
}
void Families::LoadCertificates ()
{
std::string certDir = i2p::fs::DataDirPath("certificates", "family");
std::vector<std::string> files;
int numCertificates = 0;
if (!i2p::fs::ReadDir(certDir, files)) {
LogPrint(eLogWarning, "Family: Can't load family certificates from ", certDir);
return;
}
for (const std::string & file : files) {
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
LogPrint(eLogWarning, "Family: ignoring file ", file);
continue;
}
LoadCertificate (file);
numCertificates++;
}
LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded");
}
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
const char * signature, const char * key)
{
uint8_t buf[50], signatureBuf[64];
size_t len = family.length (), signatureLen = strlen (signature);
memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32;
Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
auto it = m_SigningKeys.find (family);
if (it != m_SigningKeys.end ())
return it->second->Verify (buf, len, signatureBuf);
// TODO: process key
return true;
}
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident)
{
auto filename = i2p::fs::DataDirPath("family", (family + ".key"));
std::string sig;
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
if (ret)
{
SSL * ssl = SSL_new (ctx);
EVP_PKEY * pkey = SSL_get_privatekey (ssl);
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
if (ecKey)
{
auto group = EC_KEY_get0_group (ecKey);
if (group)
{
int curve = EC_GROUP_get_curve_name (group);
if (curve == NID_X9_62_prime256v1)
{
uint8_t signingPrivateKey[32], buf[50], signature[64];
i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32);
i2p::crypto::ECDSAP256Signer signer (signingPrivateKey);
size_t len = family.length ();
memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32;
signer.Sign (buf, len, signature);
len = Base64EncodingBufferSize (64);
char * b64 = new char[len+1];
len = ByteStreamToBase64 (signature, 64, b64, len);
b64[len] = 0;
sig = b64;
delete[] b64;
}
else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
}
}
SSL_free (ssl);
}
else
LogPrint (eLogError, "Family: Can't open keys file: ", filename);
SSL_CTX_free (ctx);
return sig;
}
}
}

View File

@@ -1,38 +0,0 @@
#ifndef FAMILY_H__
#define FAMILY_H__
#include <map>
#include <string>
#include <memory>
#include "Signature.h"
#include "Identity.h"
namespace i2p
{
namespace data
{
class Families
{
public:
Families ();
~Families ();
void LoadCertificates ();
bool VerifyFamily (const std::string& family, const IdentHash& ident,
const char * signature, const char * key = nullptr);
private:
void LoadCertificate (const std::string& filename);
private:
std::map<std::string, std::shared_ptr<i2p::crypto::Verifier> > m_SigningKeys;
};
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident);
// return base64 signature of empty string in case of failure
}
}
#endif

View File

@@ -1,650 +0,0 @@
#include <inttypes.h>
#include "I2PEndian.h"
#include <map>
#include <string>
#include "Crypto.h"
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "Tunnel.h"
#include "TunnelPool.h"
#include "Timestamp.h"
#include "Log.h"
#include "Garlic.h"
namespace i2p
{
namespace garlic
{
GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner,
std::shared_ptr<const i2p::data::RoutingDestination> destination, int numTags, bool attachLeaseSet):
m_Owner (owner), m_Destination (destination), m_NumTags (numTags),
m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend),
m_ElGamalEncryption (new i2p::crypto::ElGamalEncryption (destination->GetEncryptionPublicKey ()))
{
// create new session tags and session key
RAND_bytes (m_SessionKey, 32);
m_Encryption.SetKey (m_SessionKey);
}
GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag):
m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend)
{
memcpy (m_SessionKey, sessionKey, 32);
m_Encryption.SetKey (m_SessionKey);
m_SessionTags.push_back (sessionTag);
m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch ();
}
GarlicRoutingSession::~GarlicRoutingSession ()
{
for (auto it: m_UnconfirmedTagsMsgs)
delete it.second;
m_UnconfirmedTagsMsgs.clear ();
}
std::shared_ptr<GarlicRoutingPath> GarlicRoutingSession::GetSharedRoutingPath ()
{
if (!m_SharedRoutingPath) return nullptr;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED ||
!m_SharedRoutingPath->outboundTunnel->IsEstablished () ||
ts*1000LL > m_SharedRoutingPath->remoteLease->endDate ||
ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT)
m_SharedRoutingPath = nullptr;
if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++;
return m_SharedRoutingPath;
}
void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path)
{
if (path && path->outboundTunnel && path->remoteLease)
{
path->updateTime = i2p::util::GetSecondsSinceEpoch ();
path->numTimesUsed = 0;
}
else
path = nullptr;
m_SharedRoutingPath = path;
}
GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags ()
{
auto tags = new UnconfirmedTags (m_NumTags);
tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch ();
for (int i = 0; i < m_NumTags; i++)
{
RAND_bytes (tags->sessionTags[i], 32);
tags->sessionTags[i].creationTime = tags->tagsCreationTime;
}
return tags;
}
void GarlicRoutingSession::MessageConfirmed (uint32_t msgID)
{
TagsConfirmed (msgID);
if (msgID == m_LeaseSetUpdateMsgID)
{
m_LeaseSetUpdateStatus = eLeaseSetUpToDate;
LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed");
}
else
CleanupExpiredTags ();
}
void GarlicRoutingSession::TagsConfirmed (uint32_t msgID)
{
auto it = m_UnconfirmedTagsMsgs.find (msgID);
if (it != m_UnconfirmedTagsMsgs.end ())
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
UnconfirmedTags * tags = it->second;
if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
{
for (int i = 0; i < tags->numTags; i++)
m_SessionTags.push_back (tags->sessionTags[i]);
}
m_UnconfirmedTagsMsgs.erase (it);
delete tags;
}
}
bool GarlicRoutingSession::CleanupExpiredTags ()
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();)
{
if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
it = m_SessionTags.erase (it);
else
it++;
}
// delete expired unconfirmed tags
for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();)
{
if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT)
{
if (m_Owner)
m_Owner->RemoveDeliveryStatusSession (it->first);
delete it->second;
it = m_UnconfirmedTagsMsgs.erase (it);
}
else
it++;
}
return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty ();
}
std::shared_ptr<I2NPMessage> GarlicRoutingSession::WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg)
{
auto m = NewI2NPMessage ();
m->Align (12); // in order to get buf aligned to 16 (12 + 4)
size_t len = 0;
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
// find non-expired tag
bool tagFound = false;
SessionTag tag;
if (m_NumTags > 0)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
while (!m_SessionTags.empty ())
{
if (ts < m_SessionTags.front ().creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
{
tag = m_SessionTags.front ();
m_SessionTags.pop_front (); // use same tag only once
tagFound = true;
break;
}
else
m_SessionTags.pop_front (); // remove expired tag
}
}
// create message
if (!tagFound) // new session
{
LogPrint (eLogWarning, "Garlic: No tags available, will use ElGamal");
if (!m_Destination)
{
LogPrint (eLogError, "Garlic: Can't use ElGamal for unknown destination");
return nullptr;
}
// create ElGamal block
ElGamalBlock elGamal;
memcpy (elGamal.sessionKey, m_SessionKey, 32);
RAND_bytes (elGamal.preIV, 32); // Pre-IV
uint8_t iv[32]; // IV is first 16 bytes
SHA256(elGamal.preIV, 32, iv);
m_ElGamalEncryption->Encrypt ((uint8_t *)&elGamal, sizeof(elGamal), buf, true);
m_Encryption.SetIV (iv);
buf += 514;
len += 514;
}
else // existing session
{
// session tag
memcpy (buf, tag, 32);
uint8_t iv[32]; // IV is first 16 bytes
SHA256(tag, 32, iv);
m_Encryption.SetIV (iv);
buf += 32;
len += 32;
}
// AES block
len += CreateAESBlock (buf, msg);
htobe32buf (m->GetPayload (), len);
m->len += len + 4;
m->FillI2NPMessageHeader (eI2NPGarlic);
return m;
}
size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg)
{
size_t blockSize = 0;
bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3);
UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr;
htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count
blockSize += 2;
if (newTags) // session tags recreated
{
for (int i = 0; i < newTags->numTags; i++)
{
memcpy (buf + blockSize, newTags->sessionTags[i], 32); // tags
blockSize += 32;
}
}
uint32_t * payloadSize = (uint32_t *)(buf + blockSize);
blockSize += 4;
uint8_t * payloadHash = buf + blockSize;
blockSize += 32;
buf[blockSize] = 0; // flag
blockSize++;
size_t len = CreateGarlicPayload (buf + blockSize, msg, newTags);
htobe32buf (payloadSize, len);
SHA256(buf + blockSize, len, payloadHash);
blockSize += len;
size_t rem = blockSize % 16;
if (rem)
blockSize += (16-rem); //padding
m_Encryption.Encrypt(buf, blockSize, buf);
return blockSize;
}
size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags)
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
uint32_t msgID;
RAND_bytes ((uint8_t *)&msgID, 4);
size_t size = 0;
uint8_t * numCloves = payload + size;
*numCloves = 0;
size++;
if (m_Owner)
{
// resubmit non-confirmed LeaseSet
if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted &&
i2p::util::GetMillisecondsSinceEpoch () > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
m_LeaseSetUpdateStatus = eLeaseSetUpdated;
// attach DeviveryStatus if necessary
if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated
{
// clove is DeliveryStatus
auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID);
if (cloveSize > 0) // successive?
{
size += cloveSize;
(*numCloves)++;
if (newTags) // new tags created
m_UnconfirmedTagsMsgs[msgID] = newTags;
m_Owner->DeliveryStatusSent (shared_from_this (), msgID);
}
else
LogPrint (eLogWarning, "Garlic: DeliveryStatus clove was not created");
}
// attach LeaseSet
if (m_LeaseSetUpdateStatus == eLeaseSetUpdated)
{
m_LeaseSetUpdateStatus = eLeaseSetSubmitted;
m_LeaseSetUpdateMsgID = msgID;
m_LeaseSetSubmissionTime = i2p::util::GetMillisecondsSinceEpoch ();
// clove if our leaseSet must be attached
auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ());
size += CreateGarlicClove (payload + size, leaseSet, false);
(*numCloves)++;
}
}
if (msg) // clove message ifself if presented
{
size += CreateGarlicClove (payload + size, msg, m_Destination ? m_Destination->IsDestination () : false);
(*numCloves)++;
}
memset (payload + size, 0, 3); // certificate of message
size += 3;
htobe32buf (payload + size, msgID); // MessageID
size += 4;
htobe64buf (payload + size, ts); // Expiration of message
size += 8;
return size;
}
size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination)
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
size_t size = 0;
if (isDestination && m_Destination)
{
buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination
size++;
memcpy (buf + size, m_Destination->GetIdentHash (), 32);
size += 32;
}
else
{
buf[size] = 0;// delivery instructions flag local
size++;
}
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
size += msg->GetLength ();
uint32_t cloveID;
RAND_bytes ((uint8_t *)&cloveID, 4);
htobe32buf (buf + size, cloveID); // CloveID
size += 4;
htobe64buf (buf + size, ts); // Expiration of clove
size += 8;
memset (buf + size, 0, 3); // certificate of clove
size += 3;
return size;
}
size_t GarlicRoutingSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID)
{
size_t size = 0;
if (m_Owner)
{
auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel ();
if (inboundTunnel)
{
buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel
size++;
// hash and tunnelID sequence is reversed for Garlic
memcpy (buf + size, inboundTunnel->GetNextIdentHash (), 32); // To Hash
size += 32;
htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID
size += 4;
// create msg
auto msg = CreateDeliveryStatusMsg (msgID);
if (m_Owner)
{
//encrypt
uint8_t key[32], tag[32];
RAND_bytes (key, 32); // random session key
RAND_bytes (tag, 32); // random session tag
m_Owner->SubmitSessionKey (key, tag);
GarlicRoutingSession garlic (key, tag);
msg = garlic.WrapSingleMessage (msg);
}
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
size += msg->GetLength ();
// fill clove
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
uint32_t cloveID;
RAND_bytes ((uint8_t *)&cloveID, 4);
htobe32buf (buf + size, cloveID); // CloveID
size += 4;
htobe64buf (buf + size, ts); // Expiration of clove
size += 8;
memset (buf + size, 0, 3); // certificate of clove
size += 3;
}
else
LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus");
}
else
LogPrint (eLogWarning, "Garlic: Missing local LeaseSet");
return size;
}
GarlicDestination::~GarlicDestination ()
{
}
void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag)
{
if (key)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
auto decryption = std::make_shared<i2p::crypto::CBCDecryption>();
decryption->SetKey (key);
m_Tags[SessionTag(tag, ts)] = decryption;
}
}
bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
{
AddSessionKey (key, tag);
return true;
}
void GarlicDestination::HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
uint8_t * buf = msg->GetPayload ();
uint32_t length = bufbe32toh (buf);
if (length > msg->GetLength ())
{
LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ());
return;
}
buf += 4; // length
auto it = m_Tags.find (SessionTag(buf));
if (it != m_Tags.end ())
{
// tag found. Use AES
if (length >= 32)
{
uint8_t iv[32]; // IV is first 16 bytes
SHA256(buf, 32, iv);
it->second->SetIV (iv);
it->second->Decrypt (buf + 32, length - 32, buf + 32);
HandleAESBlock (buf + 32, length - 32, it->second, msg->from);
}
else
LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes");
m_Tags.erase (it); // tag might be used only once
}
else
{
// tag not found. Use ElGamal
ElGamalBlock elGamal;
if (length >= 514 && i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, true))
{
auto decryption = std::make_shared<i2p::crypto::CBCDecryption>();
decryption->SetKey (elGamal.sessionKey);
uint8_t iv[32]; // IV is first 16 bytes
SHA256(elGamal.preIV, 32, iv);
decryption->SetIV (iv);
decryption->Decrypt(buf + 514, length - 514, buf + 514);
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
}
else
LogPrint (eLogError, "Garlic: Failed to decrypt message");
}
}
void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
uint16_t tagCount = bufbe16toh (buf);
buf += 2; len -= 2;
if (tagCount > 0)
{
if (tagCount*32 > len)
{
LogPrint (eLogError, "Garlic: Tag count ", tagCount, " exceeds length ", len);
return ;
}
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (int i = 0; i < tagCount; i++)
m_Tags[SessionTag(buf + i*32, ts)] = decryption;
}
buf += tagCount*32;
len -= tagCount*32;
uint32_t payloadSize = bufbe32toh (buf);
if (payloadSize > len)
{
LogPrint (eLogError, "Garlic: Unexpected payload size ", payloadSize);
return;
}
buf += 4;
uint8_t * payloadHash = buf;
buf += 32;// payload hash.
if (*buf) // session key?
buf += 32; // new session key
buf++; // flag
// payload
uint8_t digest[32];
SHA256 (buf, payloadSize, digest);
if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match
{
LogPrint (eLogError, "Garlic: wrong payload hash");
return;
}
HandleGarlicPayload (buf, payloadSize, from);
}
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
const uint8_t * buf1 = buf;
int numCloves = buf[0];
LogPrint (eLogDebug, "Garlic: ", numCloves," cloves");
buf++;
for (int i = 0; i < numCloves; i++)
{
// delivery instructions
uint8_t flag = buf[0];
buf++; // flag
if (flag & 0x80) // encrypted?
{
// TODO: implement
LogPrint (eLogWarning, "Garlic: clove encrypted");
buf += 32;
}
GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03);
switch (deliveryType)
{
case eGarlicDeliveryTypeLocal:
LogPrint (eLogDebug, "Garlic: type local");
HandleI2NPMessage (buf, len, from);
break;
case eGarlicDeliveryTypeDestination:
LogPrint (eLogDebug, "Garlic: type destination");
buf += 32; // destination. check it later or for multiple destinations
HandleI2NPMessage (buf, len, from);
break;
case eGarlicDeliveryTypeTunnel:
{
LogPrint (eLogDebug, "Garlic: type tunnel");
// gwHash and gwTunnel sequence is reverted
uint8_t * gwHash = buf;
buf += 32;
uint32_t gwTunnel = bufbe32toh (buf);
buf += 4;
std::shared_ptr<i2p::tunnel::OutboundTunnel> tunnel;
if (from && from->GetTunnelPool ())
tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel ();
if (tunnel) // we have send it through an outbound tunnel
{
auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from);
tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg);
}
else
LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove");
break;
}
case eGarlicDeliveryTypeRouter:
LogPrint (eLogWarning, "Garlic: type router not supported");
buf += 32;
break;
default:
LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType);
}
buf += GetI2NPMessageLength (buf); // I2NP
buf += 4; // CloveID
buf += 8; // Date
buf += 3; // Certificate
if (buf - buf1 > (int)len)
{
LogPrint (eLogError, "Garlic: clove is too long");
break;
}
}
}
std::shared_ptr<I2NPMessage> GarlicDestination::WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet)
{
auto session = GetRoutingSession (destination, attachLeaseSet);
return session->WrapSingleMessage (msg);
}
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
{
GarlicRoutingSessionPtr session;
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto it = m_Sessions.find (destination->GetIdentHash ());
if (it != m_Sessions.end ())
session = it->second;
}
if (!session)
{
session = std::make_shared<GarlicRoutingSession> (this, destination,
attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests
std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions[destination->GetIdentHash ()] = session;
}
return session;
}
void GarlicDestination::CleanupExpiredTags ()
{
// incoming
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
int numExpiredTags = 0;
for (auto it = m_Tags.begin (); it != m_Tags.end ();)
{
if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
{
numExpiredTags++;
it = m_Tags.erase (it);
}
else
it++;
}
if (numExpiredTags > 0)
LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ());
// outgoing
std::unique_lock<std::mutex> l(m_SessionsMutex);
for (auto it = m_Sessions.begin (); it != m_Sessions.end ();)
{
it->second->GetSharedRoutingPath (); // delete shared path if necessary
if (!it->second->CleanupExpiredTags ())
{
LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted");
it = m_Sessions.erase (it);
}
else
it++;
}
}
void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID)
{
m_DeliveryStatusSessions.erase (msgID);
}
void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID)
{
m_DeliveryStatusSessions[msgID] = session;
}
void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload ());
{
auto it = m_DeliveryStatusSessions.find (msgID);
if (it != m_DeliveryStatusSessions.end ())
{
it->second->MessageConfirmed (msgID);
m_DeliveryStatusSessions.erase (it);
LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged");
}
}
}
void GarlicDestination::SetLeaseSetUpdated ()
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
for (auto it: m_Sessions)
it.second->SetLeaseSetUpdated ();
}
void GarlicDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
HandleGarlicMessage (msg);
}
void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
HandleDeliveryStatusMessage (msg);
}
}
}

201
Garlic.h
View File

@@ -1,201 +0,0 @@
#ifndef GARLIC_H__
#define GARLIC_H__
#include <inttypes.h>
#include <map>
#include <list>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include "Crypto.h"
#include "I2NPProtocol.h"
#include "LeaseSet.h"
#include "Queue.h"
#include "Identity.h"
namespace i2p
{
namespace tunnel
{
class OutboundTunnel;
}
namespace garlic
{
enum GarlicDeliveryType
{
eGarlicDeliveryTypeLocal = 0,
eGarlicDeliveryTypeDestination = 1,
eGarlicDeliveryTypeRouter = 2,
eGarlicDeliveryTypeTunnel = 3
};
struct ElGamalBlock
{
uint8_t sessionKey[32];
uint8_t preIV[32];
uint8_t padding[158];
};
const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds
const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used
struct SessionTag: public i2p::data::Tag<32>
{
SessionTag (const uint8_t * buf, uint32_t ts = 0): Tag<32>(buf), creationTime (ts) {};
SessionTag () = default;
SessionTag (const SessionTag& ) = default;
SessionTag& operator= (const SessionTag& ) = default;
#ifndef _WIN32
SessionTag (SessionTag&& ) = default;
SessionTag& operator= (SessionTag&& ) = default;
#endif
uint32_t creationTime; // seconds since epoch
};
struct GarlicRoutingPath
{
std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
std::shared_ptr<const i2p::data::Lease> remoteLease;
int rtt; // RTT
uint32_t updateTime; // seconds since epoch
int numTimesUsed;
};
class GarlicDestination;
class GarlicRoutingSession: public std::enable_shared_from_this<GarlicRoutingSession>
{
enum LeaseSetUpdateStatus
{
eLeaseSetUpToDate = 0,
eLeaseSetUpdated,
eLeaseSetSubmitted,
eLeaseSetDoNotSend
};
struct UnconfirmedTags
{
UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; };
~UnconfirmedTags () { delete[] sessionTags; };
int numTags;
SessionTag * sessionTags;
uint32_t tagsCreationTime;
};
public:
GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr<const i2p::data::RoutingDestination> destination,
int numTags, bool attachLeaseSet);
GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption
~GarlicRoutingSession ();
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
void MessageConfirmed (uint32_t msgID);
bool CleanupExpiredTags (); // returns true if something left
void SetLeaseSetUpdated ()
{
if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
};
std::shared_ptr<GarlicRoutingPath> GetSharedRoutingPath ();
void SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path);
private:
size_t CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg);
size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags);
size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination);
size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID);
void TagsConfirmed (uint32_t msgID);
UnconfirmedTags * GenerateSessionTags ();
private:
GarlicDestination * m_Owner;
std::shared_ptr<const i2p::data::RoutingDestination> m_Destination;
i2p::crypto::AESKey m_SessionKey;
std::list<SessionTag> m_SessionTags;
int m_NumTags;
std::map<uint32_t, UnconfirmedTags *> m_UnconfirmedTagsMsgs;
LeaseSetUpdateStatus m_LeaseSetUpdateStatus;
uint32_t m_LeaseSetUpdateMsgID;
uint64_t m_LeaseSetSubmissionTime; // in milliseconds
i2p::crypto::CBCEncryption m_Encryption;
std::unique_ptr<const i2p::crypto::ElGamalEncryption> m_ElGamalEncryption;
std::shared_ptr<GarlicRoutingPath> m_SharedRoutingPath;
public:
// for HTTP only
size_t GetNumOutgoingTags () const { return m_SessionTags.size (); };
};
//using GarlicRoutingSessionPtr = std::shared_ptr<GarlicRoutingSession>;
typedef std::shared_ptr<GarlicRoutingSession> GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8
class GarlicDestination: public i2p::data::LocalDestination
{
public:
GarlicDestination (): m_NumTags (32) {}; // 32 tags by default
~GarlicDestination ();
void SetNumTags (int numTags) { m_NumTags = numTags; };
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
void CleanupExpiredTags ();
void RemoveDeliveryStatusSession (uint32_t msgID);
std::shared_ptr<I2NPMessage> WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet = false);
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread
void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID);
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
virtual void SetLeaseSetUpdated ();
virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from) = 0;
protected:
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
private:
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from);
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
private:
// outgoing sessions
int m_NumTags;
std::mutex m_SessionsMutex;
std::map<i2p::data::IdentHash, GarlicRoutingSessionPtr> m_Sessions;
// incoming
std::map<SessionTag, std::shared_ptr<i2p::crypto::CBCDecryption>> m_Tags;
// DeliveryStatus
std::map<uint32_t, GarlicRoutingSessionPtr> m_DeliveryStatusSessions; // msgID -> session
public:
// for HTTP only
size_t GetNumIncomingTags () const { return m_Tags.size (); }
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
};
}
}
#endif

420
HTTP.cpp
View File

@@ -1,420 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "util.h"
#include "HTTP.h"
#include <algorithm>
#include <ctime>
namespace i2p {
namespace http {
const std::vector<std::string> HTTP_METHODS = {
"GET", "HEAD", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS", "CONNECT"
};
const std::vector<std::string> HTTP_VERSIONS = {
"HTTP/1.0", "HTTP/1.1"
};
inline bool is_http_version(const std::string & str) {
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
}
inline bool is_http_method(const std::string & str) {
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
}
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
std::size_t count = 0;
std::stringstream ss(line);
std::string token;
while (1) {
count++;
if (limit > 0 && count >= limit)
delim = '\n'; /* reset delimiter */
if (!std::getline(ss, token, delim))
break;
tokens.push_back(token);
}
}
bool parse_header_line(const std::string & line, std::map<std::string, std::string> & headers) {
std::size_t pos = 0;
std::size_t len = 2; /* strlen(": ") */
std::size_t max = line.length();
if ((pos = line.find(": ", pos)) == std::string::npos)
return false;
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
std::string name = line.substr(0, pos);
std::string value = line.substr(pos + len);
headers[name] = value;
return true;
}
void gen_rfc1123_date(std::string & out) {
std::time_t now = std::time(nullptr);
char buf[128];
std::strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&now));
out = buf;
}
bool URL::parse(const char *str, std::size_t len) {
std::string url(str, len ? len : strlen(str));
return parse(url);
}
bool URL::parse(const std::string& url) {
std::size_t pos_p = 0; /* < current parse position */
std::size_t pos_c = 0; /* < work position */
if (url.at(0) != '/') {
/* schema */
pos_c = url.find("://");
if (pos_c != std::string::npos) {
schema = url.substr(0, pos_c);
pos_p = pos_c + 3;
}
/* user[:pass] */
pos_c = url.find('@', pos_p);
if (pos_c != std::string::npos) {
std::size_t delim = url.find(':', pos_p);
if (delim != std::string::npos && delim < pos_c) {
user = url.substr(pos_p, delim - pos_p);
delim += 1;
pass = url.substr(delim, pos_c - delim);
} else {
user = url.substr(pos_p, pos_c - pos_p);
}
pos_p = pos_c + 1;
}
/* hostname[:port][/path] */
pos_c = url.find_first_of(":/", pos_p);
if (pos_c == std::string::npos) {
/* only hostname, without post and path */
host = url.substr(pos_p, std::string::npos);
return true;
} else if (url.at(pos_c) == ':') {
host = url.substr(pos_p, pos_c - pos_p);
/* port[/path] */
pos_p = pos_c + 1;
pos_c = url.find('/', pos_p);
std::string port_str = (pos_c == std::string::npos)
? url.substr(pos_p, std::string::npos)
: url.substr(pos_p, pos_c - pos_p);
/* stoi throws exception on failure, we don't need it */
for (char c : port_str) {
if (c < '0' || c > '9')
return false;
port *= 10;
port += c - '0';
}
if (pos_c == std::string::npos)
return true; /* no path part */
pos_p = pos_c;
} else {
/* start of path part found */
host = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c;
}
}
/* pos_p now at start of path part */
pos_c = url.find_first_of("?#", pos_p);
if (pos_c == std::string::npos) {
/* only path, without fragment and query */
path = url.substr(pos_p, std::string::npos);
return true;
} else if (url.at(pos_c) == '?') {
/* found query part */
path = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
pos_c = url.find('#', pos_p);
if (pos_c == std::string::npos) {
/* no fragment */
query = url.substr(pos_p, std::string::npos);
return true;
} else {
query = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
}
} else {
/* found fragment part */
path = url.substr(pos_p, pos_c - pos_p);
pos_p = pos_c + 1;
}
/* pos_p now at start of fragment part */
frag = url.substr(pos_p, std::string::npos);
return true;
}
bool URL::parse_query(std::map<std::string, std::string> & params) {
std::vector<std::string> tokens;
strsplit(query, tokens, '&');
params.clear();
for (auto it : tokens) {
std::size_t eq = it.find ('=');
if (eq != std::string::npos) {
auto e = std::pair<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
params.insert(e);
} else {
auto e = std::pair<std::string, std::string>(it, "");
params.insert(e);
}
}
return true;
}
std::string URL::to_string() {
std::string out = "";
if (schema != "") {
out = schema + "://";
if (user != "" && pass != "") {
out += user + ":" + pass + "@";
} else if (user != "") {
out += user + "@";
}
if (port) {
out += host + ":" + std::to_string(port);
} else {
out += host;
}
}
out += path;
if (query != "")
out += "?" + query;
if (frag != "")
out += "#" + frag;
return out;
}
void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
add_header(name, value.c_str(), replace);
}
void HTTPMsg::add_header(const char *name, const char *value, bool replace) {
std::size_t count = headers.count(name);
if (count && !replace)
return;
if (count) {
headers[name] = value;
return;
}
headers.insert(std::pair<std::string, std::string>(name, value));
}
void HTTPMsg::del_header(const char *name) {
headers.erase(name);
}
int HTTPReq::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPReq::parse(const std::string& str) {
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
URL url;
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == REQ_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ');
if (tokens.size() != 3)
return -1;
if (!is_http_method(tokens[0]))
return -1;
if (!is_http_version(tokens[2]))
return -1;
if (!url.parse(tokens[1]))
return -1;
/* all ok */
method = tokens[0];
uri = tokens[1];
version = tokens[2];
expect = HEADER_LINE;
} else {
std::string line = str.substr(pos, eol - pos);
if (!parse_header_line(line, headers))
return -1;
}
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + strlen(HTTP_EOH);
}
std::string HTTPReq::to_string() {
std::stringstream ss;
ss << method << " " << uri << " " << version << CRLF;
for (auto & h : headers) {
ss << h.first << ": " << h.second << CRLF;
}
ss << CRLF;
return ss.str();
}
bool HTTPRes::is_chunked() {
auto it = headers.find("Transfer-Encoding");
if (it == headers.end())
return false;
if (it->second.find("chunked") == std::string::npos)
return true;
return false;
}
long int HTTPMsg::length() {
unsigned long int length = 0;
auto it = headers.find("Content-Length");
if (it == headers.end())
return -1;
errno = 0;
length = std::strtoul(it->second.c_str(), (char **) NULL, 10);
if (errno != 0)
return -1;
return length;
}
int HTTPRes::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
}
int HTTPRes::parse(const std::string& str) {
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == RES_LINE) {
std::string line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3)
return -1;
if (!is_http_version(tokens[0]))
return -1;
code = atoi(tokens[1].c_str());
if (code < 100 || code >= 600)
return -1;
/* all ok */
version = tokens[0];
status = tokens[2];
expect = HEADER_LINE;
} else {
std::string line = str.substr(pos, eol - pos);
if (!parse_header_line(line, headers))
return -1;
}
pos = eol + strlen(CRLF);
if (pos >= eoh)
break;
}
return eoh + strlen(HTTP_EOH);
}
std::string HTTPRes::to_string() {
if (version == "HTTP/1.1" && headers.count("Date") == 0) {
std::string date;
gen_rfc1123_date(date);
add_header("Date", date.c_str());
}
if (status == "OK" && code != 200)
status = HTTPCodeToStatus(code); // update
if (body.length() > 0 && headers.count("Content-Length") == 0)
add_header("Content-Length", std::to_string(body.length()).c_str());
/* build response */
std::stringstream ss;
ss << version << " " << code << " " << status << CRLF;
for (auto & h : headers) {
ss << h.first << ": " << h.second << CRLF;
}
ss << CRLF;
if (body.length() > 0)
ss << body;
return ss.str();
}
const char * HTTPCodeToStatus(int code) {
const char *ptr;
switch (code) {
case 105: ptr = "Name Not Resolved"; break;
/* success */
case 200: ptr = "OK"; break;
case 206: ptr = "Partial Content"; break;
/* redirect */
case 301: ptr = "Moved Permanently"; break;
case 302: ptr = "Found"; break;
case 304: ptr = "Not Modified"; break;
case 307: ptr = "Temporary Redirect"; break;
/* client error */
case 400: ptr = "Bad Request"; break;
case 401: ptr = "Unauthorized"; break;
case 403: ptr = "Forbidden"; break;
case 404: ptr = "Not Found"; break;
case 407: ptr = "Proxy Authentication Required"; break;
case 408: ptr = "Request Timeout"; break;
/* server error */
case 500: ptr = "Internal Server Error"; break;
case 502: ptr = "Bad Gateway"; break;
case 503: ptr = "Not Implemented"; break;
case 504: ptr = "Gateway Timeout"; break;
default: ptr = "Unknown Status"; break;
}
return ptr;
}
std::string UrlDecode(const std::string& data, bool allow_null) {
std::string decoded(data);
size_t pos = 0;
while ((pos = decoded.find('%', pos)) != std::string::npos) {
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
if (c == '\0' && !allow_null) {
pos += 3;
continue;
}
decoded.replace(pos, 3, 1, c);
pos++;
}
return decoded;
}
bool MergeChunkedResponse (std::istream& in, std::ostream& out) {
std::string hexLen;
while (!in.eof ()) {
std::getline (in, hexLen);
errno = 0;
long int len = strtoul(hexLen.c_str(), (char **) NULL, 16);
if (errno != 0)
return false; /* conversion error */
if (len == 0)
return true; /* end of stream */
if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */
return false; /* too large chunk */
char * buf = new char[len];
in.read (buf, len);
out.write (buf, len);
delete[] buf;
std::getline (in, hexLen); // read \r\n after chunk
}
return true;
}
} // http
} // i2p

148
HTTP.h
View File

@@ -1,148 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef HTTP_H__
#define HTTP_H__
#include <cstring>
#include <map>
#include <sstream>
#include <string>
#include <vector>
namespace i2p {
namespace http {
const char CRLF[] = "\r\n"; /**< HTTP line terminator */
const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
extern const std::vector<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
struct URL {
std::string schema;
std::string user;
std::string pass;
std::string host;
unsigned short int port;
std::string path;
std::string query;
std::string frag;
URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {};
/**
* @brief Tries to parse url from string
* @return true on success, false on invalid url
*/
bool parse (const char *str, std::size_t len = 0);
bool parse (const std::string& url);
/**
* @brief Parse query part of url to key/value map
* @note Honestly, this should be implemented with std::multimap
*/
bool parse_query(std::map<std::string, std::string> & params);
/**
* @brief Serialize URL structure to url
* @note Returns relative url if schema if empty, absolute url otherwise
*/
std::string to_string ();
};
struct HTTPMsg {
std::map<std::string, std::string> headers;
void add_header(const char *name, std::string & value, bool replace = false);
void add_header(const char *name, const char *value, bool replace = false);
void del_header(const char *name);
/** @brief Returns declared message length or -1 if unknown */
long int length();
};
struct HTTPReq : HTTPMsg {
std::string version;
std::string method;
std::string uri;
HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {};
/**
* @brief Tries to parse HTTP request from string
* @return -1 on error, 0 on incomplete query, >0 on success
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
/** @brief Serialize HTTP request to string */
std::string to_string();
};
struct HTTPRes : HTTPMsg {
std::string version;
std::string status;
unsigned short int code;
/**
* @brief Simplifies response generation
*
* If this variable is set, on @a to_string() call:
* * Content-Length header will be added if missing,
* * contents of @a body will be included in generated response
*/
std::string body;
HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {}
/**
* @brief Tries to parse HTTP response from string
* @return -1 on error, 0 on incomplete query, >0 on success
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
/**
* @brief Serialize HTTP response to string
* @note If @a version is set to HTTP/1.1, and Date header is missing,
* it will be generated based on current time and added to headers
* @note If @a body is set and Content-Length header is missing,
* this header will be added, based on body's length
*/
std::string to_string();
/** @brief Checks that response declared as chunked data */
bool is_chunked();
};
/**
* @brief returns HTTP status string by integer code
* @param code HTTP code [100, 599]
* @return Immutable string with status
*/
const char * HTTPCodeToStatus(int code);
/**
* @brief Replaces %-encoded characters in string with their values
* @param data Source string
* @param null If set to true - decode also %00 sequence, otherwise - skip
* @return Decoded string
*/
std::string UrlDecode(const std::string& data, bool null = false);
/**
* @brief Merge HTTP response content with Transfer-Encoding: chunked
* @param in Input stream
* @param out Output stream
* @return true on success, false otherwise
*/
bool MergeChunkedResponse (std::istream& in, std::ostream& out);
} // http
} // i2p
#endif /* HTTP_H__ */

View File

@@ -1,349 +0,0 @@
#include <cstring>
#include <cassert>
#include <boost/lexical_cast.hpp>
#include <string>
#include <atomic>
#include "HTTPProxy.h"
#include "util.h"
#include "Identity.h"
#include "Streaming.h"
#include "Destination.h"
#include "ClientContext.h"
#include "I2PEndian.h"
#include "I2PTunnel.h"
#include "Config.h"
#include "HTTP.h"
namespace i2p
{
namespace proxy
{
static const size_t http_buffer_size = 8192;
class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<HTTPProxyHandler>
{
private:
enum state
{
GET_METHOD,
GET_HOSTNAME,
GET_HTTPV,
GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed
DONE
};
void EnterState(state nstate);
bool HandleData(uint8_t *http_buff, std::size_t len);
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void Terminate();
void AsyncSockRead();
void HTTPRequestFailed(/*std::string message*/);
void RedirectToJumpService();
void ExtractRequest();
bool IsI2PAddress();
bool ValidateHTTPRequest();
void HandleJumpServices();
bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len);
void SentHTTPFailed(const boost::system::error_code & ecode);
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
uint8_t m_http_buff[http_buffer_size];
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock;
std::string m_request; //Data left to be sent
std::string m_url; //URL
std::string m_method; //Method
std::string m_version; //HTTP version
std::string m_address; //Address
std::string m_path; //Path
int m_port; //Port
state m_state;//Parsing state
public:
HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) :
I2PServiceHandler(parent), m_sock(sock)
{ EnterState(GET_METHOD); }
~HTTPProxyHandler() { Terminate(); }
void Handle () { AsyncSockRead(); }
};
void HTTPProxyHandler::AsyncSockRead()
{
LogPrint(eLogDebug, "HTTPProxy: async sock read");
if(m_sock) {
m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size),
std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError, "HTTPProxy: no socket for read");
}
}
void HTTPProxyHandler::Terminate() {
if (Kill()) return;
if (m_sock)
{
LogPrint(eLogDebug, "HTTPProxy: close sock");
m_sock->close();
m_sock = nullptr;
}
Done(shared_from_this());
}
/* All hope is lost beyond this point */
//TODO: handle this apropriately
void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/)
{
static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n\r\n";
boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()),
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
}
void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/)
{
std::stringstream response;
std::string httpAddr; i2p::config::GetOption("http.address", httpAddr);
uint16_t httpPort; i2p::config::GetOption("http.port", httpPort);
response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n";
boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()),
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
}
void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate)
{
m_state = nstate;
}
void HTTPProxyHandler::ExtractRequest()
{
LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url);
i2p::http::URL url;
url.parse (m_url);
m_address = url.host;
m_port = url.port;
m_path = url.path;
if (url.query.length () > 0) m_path += "?" + url.query;
if (!m_port) m_port = 80;
LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path);
}
bool HTTPProxyHandler::ValidateHTTPRequest()
{
if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" )
{
LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version);
HTTPRequestFailed(); //TODO: send right stuff
return false;
}
return true;
}
void HTTPProxyHandler::HandleJumpServices()
{
static const char * helpermark1 = "?i2paddresshelper=";
static const char * helpermark2 = "&i2paddresshelper=";
size_t addressHelperPos1 = m_path.rfind (helpermark1);
size_t addressHelperPos2 = m_path.rfind (helpermark2);
size_t addressHelperPos;
if (addressHelperPos1 == std::string::npos)
{
if (addressHelperPos2 == std::string::npos)
return; //Not a jump service
else
addressHelperPos = addressHelperPos2;
}
else
{
if (addressHelperPos2 == std::string::npos)
addressHelperPos = addressHelperPos1;
else if ( addressHelperPos1 > addressHelperPos2 )
addressHelperPos = addressHelperPos1;
else
addressHelperPos = addressHelperPos2;
}
auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1));
base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded
LogPrint (eLogInfo, "HTTPProxy: jump service for ", m_address, ", inserting to address book");
//TODO: this is very dangerous and broken. We should ask the user before doing anything see http://pastethis.i2p/raw/pn5fL4YNJL7OSWj3Sc6N/
//TODO: we could redirect the user again to avoid dirtiness in the browser
i2p::client::context.GetAddressBook ().InsertAddress (m_address, base64);
m_path.erase(addressHelperPos);
}
bool HTTPProxyHandler::IsI2PAddress()
{
auto pos = m_address.rfind (".i2p");
if (pos != std::string::npos && (pos+4) == m_address.length ())
{
return true;
}
return false;
}
bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len)
{
ExtractRequest(); //TODO: parse earlier
if (!ValidateHTTPRequest()) return false;
HandleJumpServices();
i2p::data::IdentHash identHash;
if (IsI2PAddress ())
{
if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){
RedirectToJumpService();
return false;
}
}
m_request = m_method;
m_request.push_back(' ');
m_request += m_path;
m_request.push_back(' ');
m_request += m_version;
m_request.push_back('\r');
m_request.push_back('\n');
m_request.append("Connection: close\r\n");
// TODO: temporary shortcut. Must be implemented properly
uint8_t * eol = nullptr;
bool isEndOfHeader = false;
while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len)))
{
if (eol)
{
*eol = 0; eol++;
if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection
{
if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent
m_request.append("User-Agent: MYOB/6.66 (AN/ON)");
else
m_request.append ((const char *)http_buff);
m_request.append ("\r\n");
}
isEndOfHeader = !http_buff[0];
auto l = eol - http_buff;
http_buff = eol;
len -= l;
if (len > 0) // \r
{
http_buff++;
len--;
}
}
}
m_request.append(reinterpret_cast<const char *>(http_buff),len);
return true;
}
bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len)
{
while (len > 0)
{
//TODO: fallback to finding HOst: header if needed
switch (m_state)
{
case GET_METHOD:
switch (*http_buff)
{
case ' ': EnterState(GET_HOSTNAME); break;
default: m_method.push_back(*http_buff); break;
}
break;
case GET_HOSTNAME:
switch (*http_buff)
{
case ' ': EnterState(GET_HTTPV); break;
default: m_url.push_back(*http_buff); break;
}
break;
case GET_HTTPV:
switch (*http_buff)
{
case '\r': EnterState(GET_HTTPVNL); break;
default: m_version.push_back(*http_buff); break;
}
break;
case GET_HTTPVNL:
switch (*http_buff)
{
case '\n': EnterState(DONE); break;
default:
LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff));
HTTPRequestFailed(); //TODO: add correct code
return false;
}
break;
default:
LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state);
HTTPRequestFailed(); //TODO: add correct code 500
return false;
}
http_buff++;
len--;
if (m_state == DONE)
return CreateHTTPRequest(http_buff,len);
}
return true;
}
void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
{
LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes");
if(ecode)
{
LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode);
Terminate();
return;
}
if (HandleData(m_http_buff, len))
{
if (m_state == DONE)
{
LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url);
GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete,
shared_from_this(), std::placeholders::_1), m_address, m_port);
}
else
AsyncSockRead();
}
}
void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode)
{
if (ecode)
LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ());
Terminate();
}
void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
if (Kill()) return;
LogPrint (eLogInfo, "HTTPProxy: New I2PTunnel connection");
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream);
GetOwner()->AddHandler (connection);
connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_request.data()), m_request.size());
Done(shared_from_this());
}
else
{
LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info");
HTTPRequestFailed(); // TODO: Send correct error message host unreachable
}
}
HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination):
TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ())
{
}
std::shared_ptr<i2p::client::I2PServiceHandler> HTTPProxyServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
return std::make_shared<HTTPProxyHandler> (this, socket);
}
}
}

View File

@@ -1,32 +0,0 @@
#ifndef HTTP_PROXY_H__
#define HTTP_PROXY_H__
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <mutex>
#include "I2PService.h"
#include "Destination.h"
namespace i2p
{
namespace proxy
{
class HTTPProxyServer: public i2p::client::TCPIPAcceptor
{
public:
HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
~HTTPProxyServer() {};
protected:
// Implements TCPIPAcceptor
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
const char* GetName() { return "HTTP Proxy"; }
};
typedef HTTPProxyServer HTTPProxy;
}
}
#endif

View File

@@ -1,782 +0,0 @@
#include <iomanip>
#include <sstream>
#include <thread>
#include <memory>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Config.h"
#include "Tunnel.h"
#include "TransitTunnel.h"
#include "Transports.h"
#include "NetDb.h"
#include "HTTP.h"
#include "LeaseSet.h"
#include "Destination.h"
#include "RouterContext.h"
#include "ClientContext.h"
#include "HTTPServer.h"
#include "Daemon.h"
#include "util.h"
// For image and info
#include "version.h"
namespace i2p {
namespace http {
const char *itoopieFavicon =
"data:image/png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv"
"8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4wOGVynO"
"EAAAIzSURBVDhPjZNdSFNhGMf3nm3n7OzMs+8JtfJGzdlgoPtoWBrkqc1OsLTMKEY3eZOQbbS6aBVYO"
"oO8CKSLXEulQtZNahAM9Cq6lS533UUaeDEEKcN/79x7kbQT/eDhfPB7/u/7Poej08JqtXoEQbhoMpmG"
"ZFn2stf/h8nEZ4aHue1SiWBlhSCV4n41NBifBINBjina8DyfzOUIVlcJtrYINjcJ3rw1oFAg4HnjHaZ"
"p4/Ppv8zPH0G5XKZNPZibO4lKpYJ8vgOqqv+uKMq/d9Hfz/0sFr3w+/3IZt2YnbWhszOAxUUv0mkCs9"
"ncyNT6hEL6dYBgY4Ngd5eger+zU7sODHA/mpubzUytj9FofLa0VGv4s9bWCCTJUGSaNvSzXT3stuHDM"
"rc3xEqF4N2CERciURyyHfgqSZKPqfuxUMyC+OKcL4YHyl28nDFAPdqDZMcQ7tPnSfURUt0jMBgMH1nL"
"fkRRDPvcLds3otfhbRTwasaE8b6He43VSrT3QW3tBT3iPdbyN3T7Ibsor988H8OxtiaMx2sB1aBbCRW"
"R1hbQhbqYXh+6QkaJn8DZyzF09x6HeiaOTC6NK9cSsFqkb3aH3cLU+tCAx9l8FoXPBUy9n8LgyCCmS9"
"MYez0Gm9P2iWna0GOcDp8KY2JhAsnbSQS6Ahh9OgrlklINeM40bWhAkBd4SLIEh8cBURLhOeiBIArVA"
"U4yTRvJItk5PRehQVFaYfpbt9PBtTmdziaXyyUzjaHT/QZBQuKHAA0UxAAAAABJRU5ErkJggg==";
const char *cssStyles =
"<style>\r\n"
" body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n"
" a { text-decoration: none; color: #894C84; }\r\n"
" a:hover { color: #FAFAFA; background: #894C84; }\r\n"
" .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n"
" .wrapper { margin: 0 auto; padding: 1em; max-width: 60em; }\r\n"
" .left { float: left; position: absolute; }\r\n"
" .right { float: left; font-size: 1em; margin-left: 13em; max-width: 46em; overflow: auto; }\r\n"
" .tunnel.established { color: #56B734; }\r\n"
" .tunnel.expiring { color: #D3AE3F; }\r\n"
" .tunnel.failed { color: #D33F3F; }\r\n"
" .tunnel.another { color: #434343; }\r\n"
" caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n"
" table { width: 100%; border-collapse: collapse; text-align: center; }\r\n"
"</style>\r\n";
const char HTTP_PAGE_TUNNELS[] = "tunnels";
const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels";
const char HTTP_PAGE_TRANSPORTS[] = "transports";
const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations";
const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination";
const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions";
const char HTTP_PAGE_SAM_SESSION[] = "sam_session";
const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels";
const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices";
const char HTTP_PAGE_COMMANDS[] = "commands";
const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels";
const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels";
const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start";
const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel";
const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate";
const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test";
const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config";
const char HTTP_PARAM_BASE32_ADDRESS[] = "b32";
const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
const char HTTP_PARAM_ADDRESS[] = "address";
std::map<std::string, std::string> jumpservices = {
{ "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" },
{ "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" },
};
void ShowUptime (std::stringstream& s, int seconds) {
int num;
if ((num = seconds / 86400) > 0) {
s << num << " days, ";
seconds -= num * 86400;
}
if ((num = seconds / 3600) > 0) {
s << num << " hours, ";
seconds -= num * 3600;
}
if ((num = seconds / 60) > 0) {
s << num << " min, ";
seconds -= num * 60;
}
s << seconds << " seconds";
}
void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, int bytes)
{
std::string state;
switch (eState) {
case i2p::tunnel::eTunnelStateBuildReplyReceived :
case i2p::tunnel::eTunnelStatePending : state = "building"; break;
case i2p::tunnel::eTunnelStateBuildFailed :
case i2p::tunnel::eTunnelStateTestFailed :
case i2p::tunnel::eTunnelStateFailed : state = "failed"; break;
case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break;
case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
default: state = "unknown"; break;
}
s << "<span class=\"tunnel " << state << "\"> " << state << "</span>, ";
s << " " << (int) (bytes / 1024) << "&nbsp;KiB<br>\r\n";
}
void ShowPageHead (std::stringstream& s)
{
s <<
"<!DOCTYPE html>\r\n"
"<html lang=\"en\">\r\n" /* TODO: Add support for locale */
" <head>\r\n"
" <meta charset=\"UTF-8\">\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
" <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n"
" <title>Purple I2P " VERSION " Webconsole</title>\r\n"
<< cssStyles <<
"</head>\r\n";
s <<
"<body>\r\n"
"<div class=header><b>i2pd</b> webconsole</div>\r\n"
"<div class=wrapper>\r\n"
"<div class=left>\r\n"
" <a href=\"/\">Main page</a><br>\r\n<br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_COMMANDS << "\">Router commands</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">Local destinations</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_TUNNELS << "\">Tunnels</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">Transit tunnels</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_TRANSPORTS << "\">Transports</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_I2P_TUNNELS << "\">I2P tunnels</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_JUMPSERVICES << "\">Jump services</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_SAM_SESSIONS << "\">SAM sessions</a><br>\r\n"
"</div>\r\n"
"<div class=right>";
}
void ShowPageTail (std::stringstream& s)
{
s <<
"</div></div>\r\n"
"</body>\r\n"
"</html>\r\n";
}
void ShowError(std::stringstream& s, const std::string& string)
{
s << "<b>ERROR:</b>&nbsp;" << string << "<br>\r\n";
}
void ShowStatus (std::stringstream& s)
{
s << "<b>Uptime:</b> ";
ShowUptime(s, i2p::context.GetUptime ());
s << "<br>\r\n";
s << "<b>Status:</b> ";
switch (i2p::context.GetStatus ())
{
case eRouterStatusOK: s << "OK"; break;
case eRouterStatusTesting: s << "Testing"; break;
case eRouterStatusFirewalled: s << "Firewalled"; break;
default: s << "Unknown";
}
s << "<br>\r\n";
auto family = i2p::context.GetFamily ();
if (family.length () > 0)
s << "<b>Family:</b> " << family << "<br>\r\n";
s << "<b>Tunnel creation success rate:</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n";
s << "<b>Received:</b> ";
s << std::fixed << std::setprecision(2);
auto numKBytesReceived = (double) i2p::transport::transports.GetTotalReceivedBytes () / 1024;
if (numKBytesReceived < 1024)
s << numKBytesReceived << " KiB";
else if (numKBytesReceived < 1024 * 1024)
s << numKBytesReceived / 1024 << " MiB";
else
s << numKBytesReceived / 1024 / 1024 << " GiB";
s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)<br>\r\n";
s << "<b>Sent:</b> ";
auto numKBytesSent = (double) i2p::transport::transports.GetTotalSentBytes () / 1024;
if (numKBytesSent < 1024)
s << numKBytesSent << " KiB";
else if (numKBytesSent < 1024 * 1024)
s << numKBytesSent / 1024 << " MiB";
else
s << numKBytesSent / 1024 / 1024 << " GiB";
s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)<br>\r\n";
s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n<br>\r\n";
s << "<b>Our external address:</b>" << "<br>\r\n" ;
for (auto address : i2p::context.GetRouterInfo().GetAddresses())
{
switch (address->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP:
if (address->host.is_v6 ())
s << "NTCP6&nbsp;&nbsp;";
else
s << "NTCP&nbsp;&nbsp;";
break;
case i2p::data::RouterInfo::eTransportSSU:
if (address->host.is_v6 ())
s << "SSU6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
else
s << "SSU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
break;
default:
s << "Unknown&nbsp;&nbsp;";
}
s << address->host.to_string() << ":" << address->port << "<br>\r\n";
}
s << "<br>\r\n<b>Routers:</b> " << i2p::data::netdb.GetNumRouters () << " ";
s << "<b>Floodfills:</b> " << i2p::data::netdb.GetNumFloodfills () << " ";
s << "<b>LeaseSets:</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n";
size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels();
clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels();
size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels();
s << "<b>Client Tunnels:</b> " << std::to_string(clientTunnelCount) << " ";
s << "<b>Transit Tunnels:</b> " << std::to_string(transitTunnelCount) << "<br>\r\n";
}
void ShowJumpServices (std::stringstream& s, const std::string& address)
{
s << "<form method=\"get\" action=\"/\">";
s << "<input type=\"hidden\" name=\"page\" value=\"jumpservices\">";
s << "<input type=\"text\" name=\"address\" value=\"" << address << "\">";
s << "<input type=\"submit\" value=\"Update\">";
s << "</form><br>\r\n";
s << "<b>Jump services for " << address << "</b>\r\n<ul>\r\n";
for (auto & js : jumpservices) {
s << " <li><a href=\"" << js.second << address << "\">" << js.first << "</a></li>\r\n";
}
s << "</ul>\r\n";
}
void ShowLocalDestinations (std::stringstream& s)
{
s << "<b>Local Destinations:</b><br>\r\n<br>\r\n";
for (auto& it: i2p::client::context.GetDestinations ())
{
auto ident = it.second->GetIdentHash ();;
s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>\r\n" << std::endl;
}
}
void ShowLocalDestination (std::stringstream& s, const std::string& b32)
{
s << "<b>Local Destination:</b><br>\r\n<br>\r\n";
i2p::data::IdentHash ident;
ident.FromBase32 (b32);
auto dest = i2p::client::context.FindLocalDestination (ident);
if (dest)
{
s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"11\" wrap=\"on\">";
s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>\r\n";
auto pool = dest->GetTunnelPool ();
if (pool)
{
s << "<b>Inbound tunnels:</b><br>\r\n";
for (auto & it : pool->GetInboundTunnels ()) {
it->Print(s);
ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ());
}
s << "<br>\r\n";
s << "<b>Outbound tunnels:</b><br>\r\n";
for (auto & it : pool->GetOutboundTunnels ()) {
it->Print(s);
ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ());
}
}
s << "<br>\r\n";
s << "<b>Tags</b><br>Incoming: " << dest->GetNumIncomingTags () << "<br>Outgoing:<br>" << std::endl;
for (auto it: dest->GetSessions ())
{
s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " ";
s << it.second->GetNumOutgoingTags () << "<br>" << std::endl;
}
s << "<br>" << std::endl;
// s << "<br>\r\n<b>Streams:</b><br>\r\n";
// for (auto it: dest->GetStreamingDestination ()->GetStreams ())
// {
// s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " ";
// s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
// s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]";
// s << "[buf:" << it.second->GetSendBufferSize () << "]";
// s << "[RTT:" << it.second->GetRTT () << "]";
// s << "[Window:" << it.second->GetWindowSize () << "]";
// s << "[Status:" << (int)it.second->GetStatus () << "]";
// s << "<br>\r\n"<< std::endl;
// }
s << "<br>\r\n<table><caption>Streams</caption><tr>";
s << "<th>StreamID</th>";
s << "<th>Destination</th>";
s << "<th>Sent</th>";
s << "<th>Received</th>";
s << "<th>Out</th>";
s << "<th>In</th>";
s << "<th>Buf</th>";
s << "<th>RTT</th>";
s << "<th>Window</th>";
s << "<th>Status</th>";
s << "</tr>";
for (auto it: dest->GetAllStreams ())
{
s << "<tr>";
s << "<td>" << it->GetSendStreamID () << "</td>";
s << "<td>" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "</td>";
s << "<td>" << it->GetNumSentBytes () << "</td>";
s << "<td>" << it->GetNumReceivedBytes () << "</td>";
s << "<td>" << it->GetSendQueueSize () << "</td>";
s << "<td>" << it->GetReceiveQueueSize () << "</td>";
s << "<td>" << it->GetSendBufferSize () << "</td>";
s << "<td>" << it->GetRTT () << "</td>";
s << "<td>" << it->GetWindowSize () << "</td>";
s << "<td>" << (int)it->GetStatus () << "</td>";
s << "</tr><br>\r\n" << std::endl;
}
}
}
void ShowTunnels (std::stringstream& s)
{
s << "<b>Queue size:</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n";
s << "<b>Inbound tunnels:</b><br>\r\n";
for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) {
it->Print(s);
ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ());
}
s << "<br>\r\n";
s << "<b>Outbound tunnels:</b><br>\r\n";
for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) {
it->Print(s);
ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ());
}
s << "<br>\r\n";
}
void ShowCommands (std::stringstream& s)
{
/* commands */
s << "<b>Router Commands</b><br>\r\n";
s << " <a href=\"/?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "\">Run peer test</a><br>\r\n";
//s << " <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n";
if (i2p::context.AcceptsTunnels ())
s << " <a href=\"/?cmd=" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << "\">Stop accepting tunnels</a><br>\r\n";
else
s << " <a href=\"/?cmd=" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << "\">Start accepting tunnels</a><br>\r\n";
#if (!defined(WIN32) && !defined(QT_GUI_LIB))
if (Daemon.gracefullShutdownInterval) {
s << " <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "\">Cancel gracefull shutdown (";
s << Daemon.gracefullShutdownInterval;
s << " seconds remains)</a><br>\r\n";
} else {
s << " <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "\">Start gracefull shutdown</a><br>\r\n";
}
s << " <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "\">Force shutdown</a><br>\r\n";
#endif
}
void ShowTransitTunnels (std::stringstream& s)
{
s << "<b>Transit tunnels:</b><br>\r\n<br>\r\n";
for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
s << it->GetTunnelID () << "";
else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
s << "" << it->GetTunnelID ();
else
s << "" << it->GetTunnelID () << "";
s << " " << it->GetNumTransmittedBytes () << "<br>\r\n";
}
}
void ShowTransports (std::stringstream& s)
{
s << "<b>Transports:</b><br>\r\n<br>\r\n";
auto ntcpServer = i2p::transport::transports.GetNTCPServer ();
if (ntcpServer)
{
s << "<b>NTCP</b><br>\r\n";
for (auto it: ntcpServer->GetNTCPSessions ())
{
if (it.second && it.second->IsEstablished ())
{
// incoming connection doesn't have remote RI
if (it.second->IsOutgoing ()) s << "";
s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": "
<< it.second->GetSocket ().remote_endpoint().address ().to_string ();
if (!it.second->IsOutgoing ()) s << "";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
s << "<br>\r\n" << std::endl;
}
}
}
auto ssuServer = i2p::transport::transports.GetSSUServer ();
if (ssuServer)
{
s << "<br>\r\n<b>SSU</b><br>\r\n";
for (auto it: ssuServer->GetSessions ())
{
auto endpoint = it.second->GetRemoteEndpoint ();
if (it.second->IsOutgoing ()) s << "";
s << endpoint.address ().to_string () << ":" << endpoint.port ();
if (!it.second->IsOutgoing ()) s << "";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
if (it.second->GetRelayTag ())
s << " [itag:" << it.second->GetRelayTag () << "]";
s << "<br>\r\n" << std::endl;
}
s << "<br>\r\n<b>SSU6</b><br>\r\n";
for (auto it: ssuServer->GetSessionsV6 ())
{
auto endpoint = it.second->GetRemoteEndpoint ();
if (it.second->IsOutgoing ()) s << "";
s << endpoint.address ().to_string () << ":" << endpoint.port ();
if (!it.second->IsOutgoing ()) s << "";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
s << "<br>\r\n" << std::endl;
}
}
}
void ShowSAMSessions (std::stringstream& s)
{
auto sam = i2p::client::context.GetSAMBridge ();
if (!sam) {
ShowError(s, "SAM disabled");
return;
}
s << "<b>SAM Sessions:</b><br>\r\n<br>\r\n";
for (auto& it: sam->GetSessions ())
{
s << "<a href=\"/?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << it.first << "\">";
s << it.first << "</a><br>\r\n" << std::endl;
}
}
void ShowSAMSession (std::stringstream& s, const std::string& id)
{
s << "<b>SAM Session:</b><br>\r\n<br>\r\n";
auto sam = i2p::client::context.GetSAMBridge ();
if (!sam) {
ShowError(s, "SAM disabled");
return;
}
auto session = sam->FindSession (id);
if (!session) {
ShowError(s, "SAM session not found");
return;
}
auto& ident = session->localDestination->GetIdentHash();
s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>\r\n";
s << "<br>\r\n";
s << "<b>Streams:</b><br>\r\n";
for (auto it: session->ListSockets())
{
switch (it->GetSocketType ())
{
case i2p::client::eSAMSocketTypeSession : s << "session"; break;
case i2p::client::eSAMSocketTypeStream : s << "stream"; break;
case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break;
default: s << "unknown"; break;
}
s << " [" << it->GetSocket ().remote_endpoint() << "]";
s << "<br>\r\n";
}
}
void ShowI2PTunnels (std::stringstream& s)
{
s << "<b>Client Tunnels:</b><br>\r\n<br>\r\n";
for (auto& it: i2p::client::context.GetClientTunnels ())
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> ⇐ ";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "<br>\r\n"<< std::endl;
}
s << "<br>\r\n<b>Server Tunnels:</b><br>\r\n<br>\r\n";
for (auto& it: i2p::client::context.GetServerTunnels ())
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> ⇒ ";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << ":" << it.second->GetLocalPort ();
s << "</a><br>\r\n"<< std::endl;
}
}
HTTPConnection::HTTPConnection (std::shared_ptr<boost::asio::ip::tcp::socket> socket):
m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0)
{
/* cache options */
i2p::config::GetOption("http.auth", needAuth);
i2p::config::GetOption("http.user", user);
i2p::config::GetOption("http.pass", pass);
}
void HTTPConnection::Receive ()
{
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
std::bind(&HTTPConnection::HandleReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode) {
if (ecode != boost::asio::error::operation_aborted)
Terminate (ecode);
return;
}
m_Buffer[bytes_transferred] = '\0';
m_BufferLen = bytes_transferred;
RunRequest();
Receive ();
}
void HTTPConnection::RunRequest ()
{
HTTPReq request;
int ret = request.parse(m_Buffer);
if (ret < 0) {
m_Buffer[0] = '\0';
m_BufferLen = 0;
return; /* error */
}
if (ret == 0)
return; /* need more data */
HandleRequest (request);
}
void HTTPConnection::Terminate (const boost::system::error_code& ecode)
{
if (ecode == boost::asio::error::operation_aborted)
return;
boost::system::error_code ignored_ec;
m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
m_Socket->close ();
}
bool HTTPConnection::CheckAuth (const HTTPReq & req) {
/* method #1: http://user:pass@127.0.0.1:7070/ */
if (req.uri.find('@') != std::string::npos) {
URL url;
if (url.parse(req.uri) && url.user == user && url.pass == pass)
return true;
}
/* method #2: 'Authorization' header sent */
if (req.headers.count("Authorization") > 0) {
std::string provided = req.headers.find("Authorization")->second;
std::string expected = user + ":" + pass;
char b64_creds[64];
std::size_t len = 0;
len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, sizeof(b64_creds));
b64_creds[len] = '\0';
expected = "Basic ";
expected += b64_creds;
if (provided == expected)
return true;
}
LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ());
return false;
}
void HTTPConnection::HandleRequest (const HTTPReq & req)
{
std::stringstream s;
std::string content;
HTTPRes res;
LogPrint(eLogDebug, "HTTPServer: request: ", req.uri);
if (needAuth && !CheckAuth(req)) {
res.code = 401;
res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\"");
SendReply(res, content);
return;
}
// Html5 head start
ShowPageHead (s);
if (req.uri.find("page=") != std::string::npos) {
HandlePage (req, res, s);
} else if (req.uri.find("cmd=") != std::string::npos) {
HandleCommand (req, res, s);
} else {
ShowStatus (s);
//res.add_header("Refresh", "5");
}
ShowPageTail (s);
res.code = 200;
content = s.str ();
SendReply (res, content);
}
void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
{
std::map<std::string, std::string> params;
std::string page("");
URL url;
url.parse(req.uri);
url.parse_query(params);
page = params["page"];
if (page == HTTP_PAGE_TRANSPORTS)
ShowTransports (s);
else if (page == HTTP_PAGE_TUNNELS)
ShowTunnels (s);
else if (page == HTTP_PAGE_COMMANDS)
ShowCommands (s);
else if (page == HTTP_PAGE_JUMPSERVICES)
ShowJumpServices (s, params["address"]);
else if (page == HTTP_PAGE_TRANSIT_TUNNELS)
ShowTransitTunnels (s);
else if (page == HTTP_PAGE_LOCAL_DESTINATIONS)
ShowLocalDestinations (s);
else if (page == HTTP_PAGE_LOCAL_DESTINATION)
ShowLocalDestination (s, params["b32"]);
else if (page == HTTP_PAGE_SAM_SESSIONS)
ShowSAMSessions (s);
else if (page == HTTP_PAGE_SAM_SESSION)
ShowSAMSession (s, params["sam_id"]);
else if (page == HTTP_PAGE_I2P_TUNNELS)
ShowI2PTunnels (s);
else {
res.code = 400;
ShowError(s, "Unknown page: " + page);
return;
}
}
void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
{
std::map<std::string, std::string> params;
std::string cmd("");
URL url;
url.parse(req.uri);
url.parse_query(params);
cmd = params["cmd"];
if (cmd == HTTP_COMMAND_RUN_PEER_TEST)
i2p::transport::transports.PeerTest ();
else if (cmd == HTTP_COMMAND_RELOAD_CONFIG)
i2p::client::context.ReloadConfig ();
else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS)
i2p::context.SetAcceptsTunnels (true);
else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS)
i2p::context.SetAcceptsTunnels (false);
else if (cmd == HTTP_COMMAND_SHUTDOWN_START) {
i2p::context.SetAcceptsTunnels (false);
#if (!defined(WIN32) && !defined(QT_GUI_LIB))
Daemon.gracefullShutdownInterval = 10*60;
#endif
} else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) {
i2p::context.SetAcceptsTunnels (true);
#if (!defined(WIN32) && !defined(QT_GUI_LIB))
Daemon.gracefullShutdownInterval = 0;
#endif
} else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) {
Daemon.running = false;
} else {
res.code = 400;
ShowError(s, "Unknown command: " + cmd);
return;
}
s << "<b>SUCCESS</b>:&nbsp;Command accepted<br><br>\r\n";
s << "<a href=\"/?page=commands\">Back to commands list</a><br>\r\n";
s << "<p>You will be redirected in 5 seconds</b>";
res.add_header("Refresh", "5; url=/?page=commands");
}
void HTTPConnection::SendReply (HTTPRes& reply, std::string& content)
{
reply.add_header("Content-Type", "text/html");
reply.body = content;
std::string res = reply.to_string();
boost::asio::async_write (*m_Socket, boost::asio::buffer(res),
std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
}
HTTPServer::HTTPServer (const std::string& address, int port):
m_Thread (nullptr), m_Work (m_Service),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port))
{
}
HTTPServer::~HTTPServer ()
{
Stop ();
}
void HTTPServer::Start ()
{
bool needAuth; i2p::config::GetOption("http.auth", needAuth);
std::string user; i2p::config::GetOption("http.user", user);
std::string pass; i2p::config::GetOption("http.pass", pass);
/* generate pass if needed */
if (needAuth && pass == "") {
char alnum[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
pass.resize(16);
for (size_t i = 0; i < pass.size(); i++) {
pass[i] = alnum[rand() % (sizeof(alnum) - 1)];
}
i2p::config::SetOption("http.pass", pass);
LogPrint(eLogInfo, "HTTPServer: password set to ", pass);
}
m_Thread = std::unique_ptr<std::thread>(new std::thread (std::bind (&HTTPServer::Run, this)));
m_Acceptor.listen ();
Accept ();
}
void HTTPServer::Stop ()
{
m_Acceptor.close();
m_Service.stop ();
if (m_Thread) {
m_Thread->join ();
m_Thread = nullptr;
}
}
void HTTPServer::Run ()
{
m_Service.run ();
}
void HTTPServer::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service);
m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this,
boost::asio::placeholders::error, newSocket));
}
void HTTPServer::HandleAccept(const boost::system::error_code& ecode,
std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
{
if (ecode)
return;
CreateConnection(newSocket);
Accept ();
}
void HTTPServer::CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
{
auto conn = std::make_shared<HTTPConnection> (newSocket);
conn->Receive ();
}
} // http
} // i2p

View File

@@ -1,67 +0,0 @@
#ifndef HTTP_SERVER_H__
#define HTTP_SERVER_H__
namespace i2p {
namespace http {
extern const char *itoopieFavicon;
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
{
public:
HTTPConnection (std::shared_ptr<boost::asio::ip::tcp::socket> socket);
void Receive ();
private:
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void Terminate (const boost::system::error_code& ecode);
void RunRequest ();
bool CheckAuth (const HTTPReq & req);
void HandleRequest (const HTTPReq & req);
void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data);
void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data);
void SendReply (HTTPRes & res, std::string & content);
private:
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
boost::asio::deadline_timer m_Timer;
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
size_t m_BufferLen;
bool needAuth;
std::string user;
std::string pass;
};
class HTTPServer
{
public:
HTTPServer (const std::string& address, int port);
~HTTPServer ();
void Start ();
void Stop ();
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode,
std::shared_ptr<boost::asio::ip::tcp::socket> newSocket);
void CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket);
private:
std::unique_ptr<std::thread> m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor;
};
} // http
} // i2p
#endif /* HTTP_SERVER_H__ */

681
I2CP.cpp
View File

@@ -1,681 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <string.h>
#include <stdlib.h>
#include <openssl/rand.h>
#include "I2PEndian.h"
#include "Log.h"
#include "Timestamp.h"
#include "LeaseSet.h"
#include "ClientContext.h"
#include "Transports.h"
#include "Signature.h"
#include "I2CP.h"
namespace i2p
{
namespace client
{
I2CPDestination::I2CPDestination (std::shared_ptr<I2CPSession> owner, std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params):
LeaseSetDestination (isPublic, &params), m_Owner (owner), m_Identity (identity)
{
}
void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key)
{
memcpy (m_EncryptionPrivateKey, key, 256);
}
void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len)
{
uint32_t length = bufbe32toh (buf);
if (length > len - 4) length = len - 4;
m_Owner->SendMessagePayloadMessage (buf + 4, length);
}
void I2CPDestination::CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
{
i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPrivateKey, tunnels); // we don't care about encryption key
m_LeaseSetExpirationTime = ls.GetExpirationTime ();
uint8_t * leases = ls.GetLeases ();
leases[-1] = tunnels.size ();
htobe16buf (leases - 3, m_Owner->GetSessionID ());
size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size ();
m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l);
}
void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len)
{
auto ls = new i2p::data::LocalLeaseSet (m_Identity, buf, len);
ls->SetExpirationTime (m_LeaseSetExpirationTime);
SetLeaseSet (ls);
}
void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce)
{
auto msg = NewI2NPMessage ();
uint8_t * buf = msg->GetPayload ();
htobe32buf (buf, len);
memcpy (buf + 4, payload, len);
msg->len += len + 4;
msg->FillI2NPMessageHeader (eI2NPData);
auto remote = FindLeaseSet (ident);
if (remote)
GetService ().post (std::bind (&I2CPDestination::SendMsg, GetSharedFromThis (), msg, remote));
else
{
auto s = GetSharedFromThis ();
RequestDestination (ident,
[s, msg, nonce](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls)
{
bool sent = s->SendMsg (msg, ls);
s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure);
}
else
s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet);
});
}
}
bool I2CPDestination::SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote)
{
auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel ();
auto leases = remote->GetNonExpiredLeases ();
if (!leases.empty () && outboundTunnel)
{
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
uint32_t i = rand () % leases.size ();
auto garlic = WrapMessage (remote, msg, true);
msgs.push_back (i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeTunnel,
leases[i]->tunnelGateway, leases[i]->tunnelID,
garlic
});
outboundTunnel->SendTunnelDataMsg (msgs);
return true;
}
else
{
if (outboundTunnel)
LogPrint (eLogWarning, "I2CP: Failed to send message. All leases expired");
else
LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels");
return false;
}
}
I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
m_Owner (owner), m_Socket (socket), m_Payload (nullptr),
m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true)
{
}
I2CPSession::~I2CPSession ()
{
delete[] m_Payload;
}
void I2CPSession::Start ()
{
ReadProtocolByte ();
}
void I2CPSession::Stop ()
{
Terminate ();
}
void I2CPSession::ReadProtocolByte ()
{
if (m_Socket)
{
auto s = shared_from_this ();
m_Socket->async_read_some (boost::asio::buffer (m_Header, 1),
[s](const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (!ecode && bytes_transferred > 0 && s->m_Header[0] == I2CP_PROTOCOL_BYTE)
s->ReceiveHeader ();
else
s->Terminate ();
});
}
}
void I2CPSession::ReceiveHeader ()
{
boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE),
boost::asio::transfer_all (),
std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
Terminate ();
else
{
m_PayloadLen = bufbe32toh (m_Header + I2CP_HEADER_LENGTH_OFFSET);
if (m_PayloadLen > 0)
{
m_Payload = new uint8_t[m_PayloadLen];
ReceivePayload ();
}
else // no following payload
{
HandleMessage ();
ReceiveHeader (); // next message
}
}
}
void I2CPSession::ReceivePayload ()
{
boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen),
boost::asio::transfer_all (),
std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
Terminate ();
else
{
HandleMessage ();
delete[] m_Payload;
m_Payload = nullptr;
m_PayloadLen = 0;
ReceiveHeader (); // next message
}
}
void I2CPSession::HandleMessage ()
{
auto handler = m_Owner.GetMessagesHandlers ()[m_Header[I2CP_HEADER_TYPE_OFFSET]];
if (handler)
(this->*handler)(m_Payload, m_PayloadLen);
else
LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]);
}
void I2CPSession::Terminate ()
{
if (m_Destination)
{
m_Destination->Stop ();
m_Destination = nullptr;
}
if (m_Socket)
{
m_Socket->close ();
m_Socket = nullptr;
}
m_Owner.RemoveSession (GetSessionID ());
LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated");
}
void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len)
{
auto socket = m_Socket;
if (socket)
{
auto l = len + I2CP_HEADER_SIZE;
uint8_t * buf = new uint8_t[l];
htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len);
buf[I2CP_HEADER_TYPE_OFFSET] = type;
memcpy (buf + I2CP_HEADER_SIZE, payload, len);
boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (),
std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, buf));
}
else
LogPrint (eLogError, "I2CP: Can't write to the socket");
}
void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf)
{
delete[] buf;
if (ecode && ecode != boost::asio::error::operation_aborted)
Terminate ();
}
std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len)
{
uint8_t l = buf[0];
if (l > len) l = len;
return std::string ((const char *)(buf + 1), l);
}
size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str)
{
auto l = str.length ();
if (l + 1 >= len) l = len - 1;
if (l > 255) l = 255; // 1 byte max
buf[0] = l;
memcpy (buf + 1, str.c_str (), l);
return l + 1;
}
void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping)
// TODO: move to Base.cpp
{
size_t offset = 0;
while (offset < len)
{
std::string param = ExtractString (buf + offset, len - offset);
offset += param.length () + 1;
if (buf[offset] != '=')
{
LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead '=' after ", param);
break;
}
offset++;
std::string value = ExtractString (buf + offset, len - offset);
offset += value.length () + 1;
if (buf[offset] != ';')
{
LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead ';' after ", value);
break;
}
offset++;
mapping.insert (std::make_pair (param, value));
}
}
void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len)
{
// get version
auto version = ExtractString (buf, len);
auto l = version.length () + 1 + 8;
uint8_t * payload = new uint8_t[l];
// set date
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
htobe64buf (payload, ts);
// echo vesrion back
PutString (payload + 8, l - 8, version);
SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l);
delete[] payload;
}
void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len)
{
RAND_bytes ((uint8_t *)&m_SessionID, 2);
auto identity = std::make_shared<i2p::data::IdentityEx>();
size_t offset = identity->FromBuffer (buf, len);
if (!offset)
{
LogPrint (eLogError, "I2CP: create session maformed identity");
SendSessionStatusMessage (3); // invalid
return;
}
uint16_t optionsSize = bufbe16toh (buf + offset);
offset += 2;
if (optionsSize > len - offset)
{
LogPrint (eLogError, "I2CP: options size ", optionsSize, "exceeds message size");
SendSessionStatusMessage (3); // invalid
return;
}
std::map<std::string, std::string> params;
ExtractMapping (buf + offset, optionsSize, params);
offset += optionsSize; // options
if (params[I2CP_PARAM_MESSAGE_RELIABILITY] == "none") m_IsSendAccepted = false;
offset += 8; // date
if (identity->Verify (buf, offset, buf + offset)) // signature
{
bool isPublic = true;
if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false;
if (!m_Destination)
{
m_Destination = std::make_shared<I2CPDestination>(shared_from_this (), identity, isPublic, params);
SendSessionStatusMessage (1); // created
LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created");
m_Destination->Start ();
}
else
{
LogPrint (eLogError, "I2CP: session already exists");
SendSessionStatusMessage (4); // refused
}
}
else
{
LogPrint (eLogError, "I2CP: create session signature verification falied");
SendSessionStatusMessage (3); // invalid
}
}
void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len)
{
SendSessionStatusMessage (0); // destroy
LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed");
if (m_Destination)
{
m_Destination->Stop ();
m_Destination = 0;
}
}
void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len)
{
// TODO: implement actual reconfiguration
SendSessionStatusMessage (2); // updated
}
void I2CPSession::SendSessionStatusMessage (uint8_t status)
{
uint8_t buf[3];
htobe16buf (buf, m_SessionID);
buf[2] = status;
SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3);
}
void I2CPSession::SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status)
{
if (!nonce) return; // don't send status with zero nonce
uint8_t buf[15];
htobe16buf (buf, m_SessionID);
htobe32buf (buf + 2, m_MessageID++);
buf[6] = (uint8_t)status;
memset (buf + 7, 0, 4); // size
htobe32buf (buf + 11, nonce);
SendI2CPMessage (I2CP_MESSAGE_STATUS_MESSAGE, buf, 15);
}
void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len)
{
uint16_t sessionID = bufbe16toh (buf);
if (sessionID == m_SessionID)
{
size_t offset = 2;
if (m_Destination)
{
offset += i2p::crypto::DSA_PRIVATE_KEY_LENGTH; // skip signing private key
// we always assume this field as 20 bytes (DSA) regardless actual size
// instead of
//offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen ();
m_Destination->SetEncryptionPrivateKey (buf + offset);
offset += 256;
m_Destination->LeaseSetCreated (buf + offset, len - offset);
}
}
else
LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID);
}
void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len)
{
uint16_t sessionID = bufbe16toh (buf);
if (sessionID == m_SessionID)
{
size_t offset = 2;
if (m_Destination)
{
i2p::data::IdentityEx identity;
offset += identity.FromBuffer (buf + offset, len - offset);
uint32_t payloadLen = bufbe32toh (buf + offset);
offset += 4;
uint32_t nonce = bufbe32toh (buf + offset + payloadLen);
if (m_IsSendAccepted)
SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted
m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce);
}
}
else
LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID);
}
void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len)
{
SendMessageMessageHandler (buf, len - 8); // ignore flags(2) and expiration(6)
}
void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len)
{
uint16_t sessionID = bufbe16toh (buf);
if (sessionID == m_SessionID || sessionID == 0xFFFF) // -1 means without session
{
uint32_t requestID = bufbe32toh (buf + 2);
//uint32_t timeout = bufbe32toh (buf + 6);
i2p::data::IdentHash ident;
switch (buf[10])
{
case 0: // hash
ident = i2p::data::IdentHash (buf + 11);
break;
case 1: // address
{
auto name = ExtractString (buf + 11, len - 11);
if (!i2p::client::context.GetAddressBook ().GetIdentHash (name, ident))
{
LogPrint (eLogError, "I2CP: address ", name, " not found");
SendHostReplyMessage (requestID, nullptr);
return;
}
break;
}
default:
LogPrint (eLogError, "I2CP: request type ", (int)buf[10], " is not supported");
SendHostReplyMessage (requestID, nullptr);
return;
}
std::shared_ptr<LeaseSetDestination> destination = m_Destination;
if(!destination) destination = i2p::client::context.GetSharedLocalDestination ();
if (destination)
{
auto ls = destination->FindLeaseSet (ident);
if (ls)
SendHostReplyMessage (requestID, ls->GetIdentity ());
else
{
auto s = shared_from_this ();
destination->RequestDestination (ident,
[s, requestID](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr);
});
}
}
else
SendHostReplyMessage (requestID, nullptr);
}
else
LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID);
}
void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> identity)
{
if (identity)
{
size_t l = identity->GetFullLen () + 7;
uint8_t * buf = new uint8_t[l];
htobe16buf (buf, m_SessionID);
htobe32buf (buf + 2, requestID);
buf[6] = 0; // result code
identity->ToBuffer (buf + 7, l - 7);
SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, l);
delete[] buf;
}
else
{
uint8_t buf[7];
htobe16buf (buf, m_SessionID);
htobe32buf (buf + 2, requestID);
buf[6] = 1; // result code
SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, 7);
}
}
void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len)
{
if (m_Destination)
{
auto ls = m_Destination->FindLeaseSet (buf);
if (ls)
{
auto l = ls->GetIdentity ()->GetFullLen ();
uint8_t * identBuf = new uint8_t[l];
ls->GetIdentity ()->ToBuffer (identBuf, l);
SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l);
delete[] identBuf;
}
else
{
auto s = shared_from_this ();
i2p::data::IdentHash ident (buf);
m_Destination->RequestDestination (ident,
[s, ident](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
if (leaseSet) // found
{
auto l = leaseSet->GetIdentity ()->GetFullLen ();
uint8_t * identBuf = new uint8_t[l];
leaseSet->GetIdentity ()->ToBuffer (identBuf, l);
s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l);
delete[] identBuf;
}
else
s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, ident, 32); // not found
});
}
}
else
SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32);
}
void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len)
{
uint8_t limits[64];
memset (limits, 0, 64);
htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound
htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound
SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64);
}
void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len)
{
// we don't use SendI2CPMessage to eliminate additional copy
auto l = len + 10 + I2CP_HEADER_SIZE;
uint8_t * buf = new uint8_t[l];
htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10);
buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE;
htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID);
htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++);
htobe32buf (buf + I2CP_HEADER_SIZE + 6, len);
memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len);
boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (),
std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, buf));
}
I2CPServer::I2CPServer (const std::string& interface, int port):
m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port))
{
memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers));
m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler;
m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler;
m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler;
m_MessagesHandlers[I2CP_RECONFIGURE_SESSION_MESSAGE] = &I2CPSession::ReconfigureSessionMessageHandler;
m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler;
m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler;
m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler;
m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler;
m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler;
m_MessagesHandlers[I2CP_GET_BANDWIDTH_LIMITS_MESSAGE] = &I2CPSession::GetBandwidthLimitsMessageHandler;
}
I2CPServer::~I2CPServer ()
{
if (m_IsRunning)
Stop ();
}
void I2CPServer::Start ()
{
Accept ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&I2CPServer::Run, this));
}
void I2CPServer::Stop ()
{
m_IsRunning = false;
m_Acceptor.cancel ();
for (auto it: m_Sessions)
it.second->Stop ();
m_Sessions.clear ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void I2CPServer::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "I2CP: runtime exception: ", ex.what ());
}
}
}
void I2CPServer::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service);
m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void I2CPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
if (!ecode && socket)
{
boost::system::error_code ec;
auto ep = socket->remote_endpoint (ec);
if (!ec)
{
LogPrint (eLogDebug, "I2CP: new connection from ", ep);
auto session = std::make_shared<I2CPSession>(*this, socket);
m_Sessions[session->GetSessionID ()] = session;
session->Start ();
}
else
LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ());
}
else
LogPrint (eLogError, "I2CP: accept error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Accept ();
}
void I2CPServer::RemoveSession (uint16_t sessionID)
{
m_Sessions.erase (sessionID);
}
}
}

196
I2CP.h
View File

@@ -1,196 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef I2CP_H__
#define I2CP_H__
#include <inttypes.h>
#include <string>
#include <memory>
#include <thread>
#include <map>
#include <boost/asio.hpp>
#include "Destination.h"
namespace i2p
{
namespace client
{
const uint8_t I2CP_PROTOCOL_BYTE = 0x2A;
const size_t I2CP_SESSION_BUFFER_SIZE = 4096;
const size_t I2CP_HEADER_LENGTH_OFFSET = 0;
const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4;
const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1;
const uint8_t I2CP_GET_DATE_MESSAGE = 32;
const uint8_t I2CP_SET_DATE_MESSAGE = 33;
const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1;
const uint8_t I2CP_RECONFIGURE_SESSION_MESSAGE = 2;
const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20;
const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3;
const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37;
const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4;
const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5;
const uint8_t I2CP_SEND_MESSAGE_EXPIRES_MESSAGE = 36;
const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31;
const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22;
const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38;
const uint8_t I2CP_HOST_REPLY_MESSAGE = 39;
const uint8_t I2CP_DEST_LOOKUP_MESSAGE = 34;
const uint8_t I2CP_DEST_REPLY_MESSAGE = 35;
const uint8_t I2CP_GET_BANDWIDTH_LIMITS_MESSAGE = 8;
const uint8_t I2CP_BANDWIDTH_LIMITS_MESSAGE = 23;
enum I2CPMessageStatus
{
eI2CPMessageStatusAccepted = 1,
eI2CPMessageStatusGuaranteedSuccess = 4,
eI2CPMessageStatusGuaranteedFailure = 5,
eI2CPMessageStatusNoLeaseSet = 21
};
// params
const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet";
const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability";
class I2CPSession;
class I2CPDestination: public LeaseSetDestination
{
public:
I2CPDestination (std::shared_ptr<I2CPSession> owner, std::shared_ptr<const i2p::data::IdentityEx> identity, bool isPublic, const std::map<std::string, std::string>& params);
void SetEncryptionPrivateKey (const uint8_t * key);
void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession
void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession
// implements LocalDestination
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Identity; };
protected:
// I2CP
void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
private:
std::shared_ptr<I2CPDestination> GetSharedFromThis ()
{ return std::static_pointer_cast<I2CPDestination>(shared_from_this ()); }
bool SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
private:
std::shared_ptr<I2CPSession> m_Owner;
std::shared_ptr<const i2p::data::IdentityEx> m_Identity;
uint8_t m_EncryptionPrivateKey[256];
uint64_t m_LeaseSetExpirationTime;
};
class I2CPServer;
class I2CPSession: public std::enable_shared_from_this<I2CPSession>
{
public:
I2CPSession (I2CPServer& owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
~I2CPSession ();
void Start ();
void Stop ();
uint16_t GetSessionID () const { return m_SessionID; };
// called from I2CPDestination
void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len);
void SendMessagePayloadMessage (const uint8_t * payload, size_t len);
void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status);
// message handlers
void GetDateMessageHandler (const uint8_t * buf, size_t len);
void CreateSessionMessageHandler (const uint8_t * buf, size_t len);
void DestroySessionMessageHandler (const uint8_t * buf, size_t len);
void ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len);
void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len);
void SendMessageMessageHandler (const uint8_t * buf, size_t len);
void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len);
void HostLookupMessageHandler (const uint8_t * buf, size_t len);
void DestLookupMessageHandler (const uint8_t * buf, size_t len);
void GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len);
private:
void ReadProtocolByte ();
void ReceiveHeader ();
void HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ReceivePayload ();
void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleMessage ();
void Terminate ();
void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf);
std::string ExtractString (const uint8_t * buf, size_t len);
size_t PutString (uint8_t * buf, size_t len, const std::string& str);
void ExtractMapping (const uint8_t * buf, size_t len, std::map<std::string, std::string>& mapping);
void SendSessionStatusMessage (uint8_t status);
void SendHostReplyMessage (uint32_t requestID, std::shared_ptr<const i2p::data::IdentityEx> identity);
private:
I2CPServer& m_Owner;
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload;
size_t m_PayloadLen;
std::shared_ptr<I2CPDestination> m_Destination;
uint16_t m_SessionID;
uint32_t m_MessageID;
bool m_IsSendAccepted;
};
typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len);
class I2CPServer
{
public:
I2CPServer (const std::string& interface, int port);
~I2CPServer ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
void RemoveSession (uint16_t sessionID);
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
private:
I2CPMessageHandler m_MessagesHandlers[256];
std::map<uint16_t, std::shared_ptr<I2CPSession> > m_Sessions;
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
public:
const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; };
};
}
}
#endif

View File

@@ -1,660 +0,0 @@
#include <string.h>
#include <atomic>
#include "Base.h"
#include "Log.h"
#include "Crypto.h"
#include "I2PEndian.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.h"
#include "Tunnel.h"
#include "Transports.h"
#include "Garlic.h"
#include "I2NPProtocol.h"
using namespace i2p::transport;
namespace i2p
{
std::shared_ptr<I2NPMessage> NewI2NPMessage ()
{
return std::make_shared<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> >();
}
std::shared_ptr<I2NPMessage> NewI2NPShortMessage ()
{
return std::make_shared<I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE> >();
}
std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len)
{
return (len < I2NP_MAX_SHORT_MESSAGE_SIZE/2) ? NewI2NPShortMessage () : NewI2NPMessage ();
}
void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID)
{
SetTypeID (msgType);
if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4);
SetMsgID (replyMsgID);
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
UpdateSize ();
UpdateChks ();
}
void I2NPMessage::RenewI2NPMessageHeader ()
{
uint32_t msgID;
RAND_bytes ((uint8_t *)&msgID, 4);
SetMsgID (msgID);
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
}
bool I2NPMessage::IsExpired () const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
auto exp = GetExpiration ();
return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
}
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID)
{
auto msg = NewI2NPMessage (len);
if (msg->Concat (buf, len) < len)
LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length ", msg->maxLen);
msg->FillI2NPMessageHeader (msgType, replyMsgID);
return msg;
}
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
auto msg = NewI2NPMessage ();
if (msg->offset + len < msg->maxLen)
{
memcpy (msg->GetBuffer (), buf, len);
msg->len = msg->offset + len;
msg->from = from;
}
else
LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length");
return msg;
}
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (!msg) return nullptr;
auto newMsg = NewI2NPMessage (msg->len);
newMsg->offset = msg->offset;
*newMsg = *msg;
return newMsg;
}
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
{
auto m = NewI2NPShortMessage ();
uint8_t * buf = m->GetPayload ();
if (msgID)
{
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, i2p::util::GetMillisecondsSinceEpoch ());
}
else // for SSU establishment
{
RAND_bytes ((uint8_t *)&msgID, 4);
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2
}
m->len += DELIVERY_STATUS_SIZE;
m->FillI2NPMessageHeader (eI2NPDeliveryStatus);
return m;
}
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers)
{
auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage ();
uint8_t * buf = m->GetPayload ();
memcpy (buf, key, 32); // key
buf += 32;
memcpy (buf, from, 32); // from
buf += 32;
uint8_t flag = exploratory ? DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP : DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP;
if (replyTunnelID)
{
*buf = flag | DATABASE_LOOKUP_DELIVERY_FLAG; // set delivery flag
htobe32buf (buf+1, replyTunnelID);
buf += 5;
}
else
{
*buf = flag; // flag
buf++;
}
if (excludedPeers)
{
int cnt = excludedPeers->size ();
htobe16buf (buf, cnt);
buf += 2;
for (auto& it: *excludedPeers)
{
memcpy (buf, it, 32);
buf += 32;
}
}
else
{
// nothing to exclude
htobuf16 (buf, 0);
buf += 2;
}
m->len += (buf - m->GetPayload ());
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
return m;
}
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
const std::set<i2p::data::IdentHash>& excludedFloodfills,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag)
{
int cnt = excludedFloodfills.size ();
auto m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage ();
uint8_t * buf = m->GetPayload ();
memcpy (buf, dest, 32); // key
buf += 32;
memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW
buf += 32;
*buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags
htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID
buf += 5;
// excluded
htobe16buf (buf, cnt);
buf += 2;
if (cnt > 0)
{
for (auto& it: excludedFloodfills)
{
memcpy (buf, it, 32);
buf += 32;
}
}
// encryption
memcpy (buf, replyKey, 32);
buf[32] = 1; // 1 tag
memcpy (buf + 33, replyTag, 32);
buf += 65;
m->len += (buf - m->GetPayload ());
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident,
std::vector<i2p::data::IdentHash> routers)
{
auto m = NewI2NPShortMessage ();
uint8_t * buf = m->GetPayload ();
size_t len = 0;
memcpy (buf, ident, 32);
len += 32;
buf[len] = routers.size ();
len++;
for (auto it: routers)
{
memcpy (buf + len, it, 32);
len += 32;
}
memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32);
len += 32;
m->len += len;
m->FillI2NPMessageHeader (eI2NPDatabaseSearchReply);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router, uint32_t replyToken)
{
if (!router) // we send own RouterInfo
router = context.GetSharedRouterInfo ();
auto m = NewI2NPShortMessage ();
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = 0; // RouterInfo
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
memset (buf, 0, 4); // zero tunnelID means direct reply
buf += 4;
memcpy (buf, router->GetIdentHash (), 32);
buf += 32;
}
uint8_t * sizePtr = buf;
buf += 2;
m->len += (buf - payload); // payload size
i2p::data::GzipDeflator deflator;
size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len);
if (size)
{
htobe16buf (sizePtr, size); // size
m->len += size;
}
else
m = nullptr;
if (m)
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
{
if (!leaseSet) return nullptr;
auto m = NewI2NPShortMessage ();
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
size_t size = DATABASE_STORE_HEADER_SIZE;
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
size += leaseSet->GetBufferLen ();
m->len += size;
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
return m;
}
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LocalLeaseSet> leaseSet, uint32_t replyToken, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
{
if (!leaseSet) return nullptr;
auto m = NewI2NPShortMessage ();
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
size_t size = DATABASE_STORE_HEADER_SIZE;
if (replyToken && replyTunnel)
{
if (replyTunnel)
{
htobe32buf (payload + size, replyTunnel->GetNextTunnelID ());
size += 4; // reply tunnelID
memcpy (payload + size, replyTunnel->GetNextIdentHash (), 32);
size += 32; // reply tunnel gateway
}
else
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
}
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
size += leaseSet->GetBufferLen ();
m->len += size;
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
return m;
}
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg)
{
if (!msg || msg->GetTypeID () != eI2NPDatabaseStore) return false;
return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo
}
static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO:
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels)
{
if (maxNumTransitTunnels > 0 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels)
{
LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels);
g_MaxNumTransitTunnels = maxNumTransitTunnels;
}
}
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
{
for (int i = 0; i < num; i++)
{
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText);
// replace record to reply
if (i2p::context.AcceptsTunnels () &&
i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels &&
!i2p::transport::transports.IsBandwidthExceeded ())
{
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80,
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40);
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0;
}
else
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30)
//TODO: fill filler
SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret
record + BUILD_RESPONSE_RECORD_HASH_OFFSET);
// encrypt reply
i2p::crypto::CBCEncryption encryption;
for (int j = 0; j < num; j++)
{
encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
}
return true;
}
}
return false;
}
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records");
if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1)
{
LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
{
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (HandleBuildRequestRecords (num, buf + 1, clearText))
{
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel
{
// so we send it to reply tunnel
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPVariableTunnelBuildReply, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
}
}
void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
{
if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE)
{
LogPrint (eLogError, "TunnelBuild message is too short ", len);
return;
}
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText))
{
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel
{
// so we send it to reply tunnel
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPTunnelBuildReply, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateI2NPMessage (eI2NPTunnelBuild, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
}
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID);
if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1)
{
LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
if (tunnel)
{
// reply for outbound tunnel
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found");
}
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf)
{
auto msg = NewI2NPShortMessage ();
msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE);
msg->FillI2NPMessageHeader (eI2NPTunnelData);
return msg;
}
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload)
{
auto msg = NewI2NPShortMessage ();
htobe32buf (msg->GetPayload (), tunnelID);
msg->len += 4; // tunnelID
msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4);
msg->FillI2NPMessageHeader (eI2NPTunnelData);
return msg;
}
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ()
{
auto msg = NewI2NPShortMessage ();
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
return msg;
}
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len)
{
auto msg = NewI2NPMessage (len);
uint8_t * payload = msg->GetPayload ();
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
msg->len += TUNNEL_GATEWAY_HEADER_SIZE;
if (msg->Concat (buf, len) < len)
LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen);
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
return msg;
}
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg)
{
if (msg->offset >= I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE)
{
// message is capable to be used without copying
uint8_t * payload = msg->GetBuffer () - TUNNEL_GATEWAY_HEADER_SIZE;
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
int len = msg->GetLength ();
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
msg->offset -= (I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE);
msg->len = msg->offset + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len;
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
return msg;
}
else
return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
}
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
const uint8_t * buf, size_t len, uint32_t replyMsgID)
{
auto msg = NewI2NPMessage (len);
size_t gatewayMsgOffset = I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
msg->offset += gatewayMsgOffset;
msg->len += gatewayMsgOffset;
if (msg->Concat (buf, len) < len)
LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen);
msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message
len = msg->GetLength ();
msg->offset -= gatewayMsgOffset;
uint8_t * payload = msg->GetPayload ();
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
msg->FillI2NPMessageHeader (eI2NPTunnelGateway); // gateway message
return msg;
}
size_t GetI2NPMessageLength (const uint8_t * msg)
{
return bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE;
}
void HandleI2NPMessage (uint8_t * msg, size_t len)
{
uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET];
uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET);
LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
uint8_t * buf = msg + I2NP_HEADER_SIZE;
int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET);
switch (typeID)
{
case eI2NPVariableTunnelBuild:
HandleVariableTunnelBuildMsg (msgID, buf, size);
break;
case eI2NPVariableTunnelBuildReply:
HandleVariableTunnelBuildReplyMsg (msgID, buf, size);
break;
case eI2NPTunnelBuild:
HandleTunnelBuildMsg (buf, size);
break;
case eI2NPTunnelBuildReply:
// TODO:
break;
default:
LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID);
}
}
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
{
uint8_t typeID = msg->GetTypeID ();
LogPrint (eLogDebug, "I2NP: Handling message with type ", (int)typeID);
switch (typeID)
{
case eI2NPTunnelData:
i2p::tunnel::tunnels.PostTunnelData (msg);
break;
case eI2NPTunnelGateway:
i2p::tunnel::tunnels.PostTunnelData (msg);
break;
case eI2NPGarlic:
{
if (msg->from)
{
if (msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg);
else
LogPrint (eLogInfo, "I2NP: Local destination for garlic doesn't exist anymore");
}
else
i2p::context.ProcessGarlicMessage (msg);
break;
}
case eI2NPDatabaseStore:
case eI2NPDatabaseSearchReply:
case eI2NPDatabaseLookup:
// forward to netDb
i2p::data::netdb.PostI2NPMsg (msg);
break;
case eI2NPDeliveryStatus:
{
if (msg->from && msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg);
else
i2p::context.ProcessDeliveryStatusMessage (msg);
break;
}
case eI2NPVariableTunnelBuild:
case eI2NPVariableTunnelBuildReply:
case eI2NPTunnelBuild:
case eI2NPTunnelBuildReply:
// forward to tunnel thread
i2p::tunnel::tunnels.PostTunnelData (msg);
break;
default:
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
}
}
}
I2NPMessagesHandler::~I2NPMessagesHandler ()
{
Flush ();
}
void I2NPMessagesHandler::PutNextMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
{
switch (msg->GetTypeID ())
{
case eI2NPTunnelData:
m_TunnelMsgs.push_back (msg);
break;
case eI2NPTunnelGateway:
m_TunnelGatewayMsgs.push_back (msg);
break;
default:
HandleI2NPMessage (msg);
}
}
}
void I2NPMessagesHandler::Flush ()
{
if (!m_TunnelMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
m_TunnelMsgs.clear ();
}
if (!m_TunnelGatewayMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
m_TunnelGatewayMsgs.clear ();
}
}
}

View File

@@ -1,266 +0,0 @@
#ifndef I2NP_PROTOCOL_H__
#define I2NP_PROTOCOL_H__
#include <inttypes.h>
#include <string.h>
#include <set>
#include <memory>
#include "Crypto.h"
#include "I2PEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "LeaseSet.h"
namespace i2p
{
// I2NP header
const size_t I2NP_HEADER_TYPEID_OFFSET = 0;
const size_t I2NP_HEADER_MSGID_OFFSET = I2NP_HEADER_TYPEID_OFFSET + 1;
const size_t I2NP_HEADER_EXPIRATION_OFFSET = I2NP_HEADER_MSGID_OFFSET + 4;
const size_t I2NP_HEADER_SIZE_OFFSET = I2NP_HEADER_EXPIRATION_OFFSET + 8;
const size_t I2NP_HEADER_CHKS_OFFSET = I2NP_HEADER_SIZE_OFFSET + 2;
const size_t I2NP_HEADER_SIZE = I2NP_HEADER_CHKS_OFFSET + 1;
// I2NP short header
const size_t I2NP_SHORT_HEADER_TYPEID_OFFSET = 0;
const size_t I2NP_SHORT_HEADER_EXPIRATION_OFFSET = I2NP_SHORT_HEADER_TYPEID_OFFSET + 1;
const size_t I2NP_SHORT_HEADER_SIZE = I2NP_SHORT_HEADER_EXPIRATION_OFFSET + 4;
// Tunnel Gateway header
const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0;
const size_t TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET = TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET + 4;
const size_t TUNNEL_GATEWAY_HEADER_SIZE = TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET + 2;
// DeliveryStatus
const size_t DELIVERY_STATUS_MSGID_OFFSET = 0;
const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4;
const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8;
// DatabaseStore
const size_t DATABASE_STORE_KEY_OFFSET = 0;
const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32;
const size_t DATABASE_STORE_REPLY_TOKEN_OFFSET = DATABASE_STORE_TYPE_OFFSET + 1;
const size_t DATABASE_STORE_HEADER_SIZE = DATABASE_STORE_REPLY_TOKEN_OFFSET + 4;
// TunnelBuild
const size_t TUNNEL_BUILD_RECORD_SIZE = 528;
//BuildRequestRecordClearText
const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0;
const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32;
const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16;
const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1;
const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4;
const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222;
// BuildRequestRecordEncrypted
const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0;
const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16;
// BuildResponseRecord
const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0;
const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32;
const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495;
const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE;
enum I2NPMessageType
{
eI2NPDatabaseStore = 1,
eI2NPDatabaseLookup = 2,
eI2NPDatabaseSearchReply = 3,
eI2NPDeliveryStatus = 10,
eI2NPGarlic = 11,
eI2NPTunnelData = 18,
eI2NPTunnelGateway = 19,
eI2NPData = 20,
eI2NPTunnelBuild = 21,
eI2NPTunnelBuildReply = 22,
eI2NPVariableTunnelBuild = 23,
eI2NPVariableTunnelBuildReply = 24
};
const int NUM_TUNNEL_BUILD_RECORDS = 8;
// DatabaseLookup flags
const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01;
const uint8_t DATABASE_LOOKUP_ENCYPTION_FLAG = 0x02;
const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C;
const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0;
const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100
const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000
const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100
namespace tunnel
{
class InboundTunnel;
class TunnelPool;
}
const size_t I2NP_MAX_MESSAGE_SIZE = 32768;
const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096;
const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
struct I2NPMessage
{
uint8_t * buf;
size_t len, offset, maxLen;
std::shared_ptr<i2p::tunnel::InboundTunnel> from;
I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2),
offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header
// header accessors
uint8_t * GetHeader () { return GetBuffer (); };
const uint8_t * GetHeader () const { return GetBuffer (); };
void SetTypeID (uint8_t typeID) { GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = typeID; };
uint8_t GetTypeID () const { return GetHeader ()[I2NP_HEADER_TYPEID_OFFSET]; };
void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); };
uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); };
void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); };
uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); };
void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); };
uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); };
void UpdateSize () { SetSize (GetPayloadLength ()); };
void SetChks (uint8_t chks) { GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = chks; };
void UpdateChks ()
{
uint8_t hash[32];
SHA256(GetPayload (), GetPayloadLength (), hash);
GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = hash[0];
}
// payload
uint8_t * GetPayload () { return GetBuffer () + I2NP_HEADER_SIZE; };
const uint8_t * GetPayload () const { return GetBuffer () + I2NP_HEADER_SIZE; };
uint8_t * GetBuffer () { return buf + offset; };
const uint8_t * GetBuffer () const { return buf + offset; };
size_t GetLength () const { return len - offset; };
size_t GetPayloadLength () const { return GetLength () - I2NP_HEADER_SIZE; };
void Align (size_t alignment)
{
if (len + alignment > maxLen) return;
size_t rem = ((size_t)GetBuffer ()) % alignment;
if (rem)
{
offset += (alignment - rem);
len += (alignment - rem);
}
}
size_t Concat (const uint8_t * buf1, size_t len1)
{
// make sure with don't write beyond maxLen
if (len + len1 > maxLen) len1 = maxLen - len;
memcpy (buf + len, buf1, len1);
len += len1;
return len1;
}
I2NPMessage& operator=(const I2NPMessage& other)
{
memcpy (buf + offset, other.buf + other.offset, other.GetLength ());
len = offset + other.GetLength ();
from = other.from;
return *this;
}
// for SSU only
uint8_t * GetSSUHeader () { return buf + offset + I2NP_HEADER_SIZE - I2NP_SHORT_HEADER_SIZE; };
void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular
{
const uint8_t * ssu = GetSSUHeader ();
GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET]; // typeid
SetMsgID (msgID);
SetExpiration (bufbe32toh (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET)*1000LL);
SetSize (len - offset - I2NP_HEADER_SIZE);
SetChks (0);
}
uint32_t ToSSU () // return msgID
{
uint8_t header[I2NP_HEADER_SIZE];
memcpy (header, GetHeader (), I2NP_HEADER_SIZE);
uint8_t * ssu = GetSSUHeader ();
ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET] = header[I2NP_HEADER_TYPEID_OFFSET]; // typeid
htobe32buf (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET, bufbe64toh (header + I2NP_HEADER_EXPIRATION_OFFSET)/1000LL);
len = offset + I2NP_SHORT_HEADER_SIZE + bufbe16toh (header + I2NP_HEADER_SIZE_OFFSET);
return bufbe32toh (header + I2NP_HEADER_MSGID_OFFSET);
}
void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0);
void RenewI2NPMessageHeader ();
bool IsExpired () const;
};
template<int sz>
struct I2NPMessageBuffer: public I2NPMessage
{
I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; };
uint8_t m_Buffer[sz + 32]; // 16 alignment + 16 padding
};
std::shared_ptr<I2NPMessage> NewI2NPMessage ();
std::shared_ptr<I2NPMessage> NewI2NPShortMessage ();
std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len);
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from = nullptr);
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg);
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory = false, std::set<i2p::data::IdentHash> * excludedPeers = nullptr);
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
const std::set<i2p::data::IdentHash>& excludedFloodfills,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag);
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, uint32_t replyToken = 0);
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet); // for floodfill only
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LocalLeaseSet> leaseSet, uint32_t replyToken = 0, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel = nullptr);
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg);
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText);
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
void HandleTunnelBuildMsg (uint8_t * buf, size_t len);
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf);
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload);
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ();
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len);
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
size_t GetI2NPMessageLength (const uint8_t * msg);
void HandleI2NPMessage (uint8_t * msg, size_t len);
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
class I2NPMessagesHandler
{
public:
~I2NPMessagesHandler ();
void PutNextMessage (std::shared_ptr<I2NPMessage> msg);
void Flush ();
private:
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
};
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500;
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
}
#endif

View File

@@ -1,563 +0,0 @@
#include <stdio.h>
#include <sstream>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/property_tree/ini_parser.hpp>
// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy
#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))
#if !GCC47_BOOST149
#include <boost/property_tree/json_parser.hpp>
#endif
#include "Crypto.h"
#include "FS.h"
#include "Log.h"
#include "HTTP.h"
#include "Config.h"
#include "NetDb.h"
#include "RouterContext.h"
#include "Daemon.h"
#include "Tunnel.h"
#include "Timestamp.h"
#include "Transports.h"
#include "version.h"
#include "util.h"
#include "I2PControl.h"
namespace i2p
{
namespace client
{
I2PControlService::I2PControlService (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
m_SSLContext (m_Service, boost::asio::ssl::context::sslv23),
m_ShutdownTimer (m_Service)
{
i2p::config::GetOption("i2pcontrol.password", m_Password);
// certificate / keys
std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt);
std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key);
if (i2pcp_crt.at(0) != '/')
i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
if (i2pcp_key.at(0) != '/')
i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) {
LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection");
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
} else {
LogPrint(eLogDebug, "I2PControl: using cert from ", i2pcp_crt);
}
m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem);
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem);
// handlers
m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler;
m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler;
m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler;
m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler;
m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler;
m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler;
// I2PControl
m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler;
// RouterInfo
m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler;
m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler;
m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler;
m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler;
m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler;
m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S;
m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S;
m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler;
m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler;
m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes;
m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes;
// RouterManager
m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler;
m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler;
m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler;
// NetworkSetting
m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit;
m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit;
}
I2PControlService::~I2PControlService ()
{
Stop ();
}
void I2PControlService::Start ()
{
if (!m_IsRunning)
{
Accept ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&I2PControlService::Run, this));
}
}
void I2PControlService::Stop ()
{
if (m_IsRunning)
{
m_IsRunning = false;
m_Acceptor.cancel ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
}
void I2PControlService::Run ()
{
while (m_IsRunning)
{
try {
m_Service.run ();
} catch (std::exception& ex) {
LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ());
}
}
}
void I2PControlService::Accept ()
{
auto newSocket = std::make_shared<ssl_socket> (m_Service, m_SSLContext);
m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
{
if (ecode != boost::asio::error::operation_aborted)
Accept ();
if (ecode) {
LogPrint (eLogError, "I2PControl: accept error: ", ecode.message ());
return;
}
LogPrint (eLogDebug, "I2PControl: new request from ", socket->lowest_layer ().remote_endpoint ());
Handshake (socket);
}
void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
{
socket->async_handshake(boost::asio::ssl::stream_base::server,
std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket));
}
void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
{
if (ecode) {
LogPrint (eLogError, "I2PControl: handshake error: ", ecode.message ());
return;
}
//std::this_thread::sleep_for (std::chrono::milliseconds(5));
ReadRequest (socket);
}
void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
{
auto request = std::make_shared<I2PControlBuffer>();
socket->async_read_some (
#if defined(BOOST_ASIO_HAS_STD_ARRAY)
boost::asio::buffer (*request),
#else
boost::asio::buffer (request->data (), request->size ()),
#endif
std::bind(&I2PControlService::HandleRequestReceived, this,
std::placeholders::_1, std::placeholders::_2, socket, request));
}
void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf)
{
if (ecode) {
LogPrint (eLogError, "I2PControl: read error: ", ecode.message ());
return;
}
/* try to parse received data */
std::stringstream json;
std::string response;
bool isHTTP = false;
if (memcmp (buf->data (), "POST", 4) == 0) {
long int remains = 0;
isHTTP = true;
i2p::http::HTTPReq req;
std::size_t len = req.parse(buf->data(), bytes_transferred);
if (len <= 0) {
LogPrint(eLogError, "I2PControl: incomplete/malformed POST request");
return;
}
/* append to json chunk of data from 1st request */
json.write(buf->begin() + len, bytes_transferred - len);
remains = req.length() - len;
/* if request has Content-Length header, fetch rest of data and store to json buffer */
while (remains > 0) {
len = ((long int) buf->size() < remains) ? buf->size() : remains;
bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), len));
json.write(buf->begin(), bytes_transferred);
remains -= bytes_transferred;
}
} else {
json.write(buf->begin(), bytes_transferred);
}
LogPrint(eLogDebug, "I2PControl: json from request: ", json.str());
#if GCC47_BOOST149
LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7");
BuildErrorResponse(response, 32603, "JSON requests is not supported with this version of boost");
#else
/* now try to parse json itself */
try {
boost::property_tree::ptree pt;
boost::property_tree::read_json (json, pt);
std::string id = pt.get<std::string>("id");
std::string method = pt.get<std::string>("method");
auto it = m_MethodHandlers.find (method);
if (it != m_MethodHandlers.end ()) {
std::ostringstream ss;
ss << "{\"id\":" << id << ",\"result\":{";
(this->*(it->second))(pt.get_child ("params"), ss);
ss << "},\"jsonrpc\":\"2.0\"}";
response = ss.str();
} else {
LogPrint (eLogWarning, "I2PControl: unknown method ", method);
BuildErrorResponse(response, 32601, "Method not found");
}
} catch (std::exception& ex) {
LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ());
BuildErrorResponse(response, 32603, ex.what());
} catch (...) {
LogPrint (eLogError, "I2PControl: handle request unknown exception");
}
#endif
SendResponse (socket, buf, response, isHTTP);
}
void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const
{
ss << "\"" << name << "\":" << value;
}
void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const
{
ss << "\"" << name << "\":";
if (value.length () > 0)
ss << "\"" << value << "\"";
else
ss << "null";
}
void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const
{
ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value;
}
void I2PControlService::BuildErrorResponse (std::string & content, int code, const char *message) {
std::stringstream ss;
ss << "{\"id\":null,\"error\":";
ss << "{\"code\":" << -code << ",\"message\":\"" << message << "\"},";
ss << "\"jsonrpc\":\"2.0\"}";
content = ss.str();
}
void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::string& content, bool isHTTP)
{
if (isHTTP) {
i2p::http::HTTPRes res;
res.code = 200;
res.add_header("Content-Type", "application/json");
res.add_header("Connection", "close");
res.body = content;
std::string tmp = res.to_string();
content = tmp;
}
std::copy(content.begin(), content.end(), buf->begin());
boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), content.length()),
boost::asio::transfer_all (),
std::bind(&I2PControlService::HandleResponseSent, this,
std::placeholders::_1, std::placeholders::_2, socket, buf));
}
void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf)
{
if (ecode) {
LogPrint (eLogError, "I2PControl: write error: ", ecode.message ());
}
}
// handlers
void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
{
int api = params.get<int> ("API");
auto password = params.get<std::string> ("Password");
LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password);
if (password != m_Password) {
LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password);
return;
}
InsertParam (results, "API", api);
results << ",";
std::string token = std::to_string(i2p::util::GetSecondsSinceEpoch ());
m_Tokens.insert (token);
InsertParam (results, "Token", token);
}
void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
{
auto echo = params.get<std::string> ("Echo");
LogPrint (eLogDebug, "I2PControl Echo Echo=", echo);
InsertParam (results, "Result", echo);
}
// I2PControl
void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
{
for (auto& it: params)
{
LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first);
auto it1 = m_I2PControlHandlers.find (it.first);
if (it1 != m_I2PControlHandlers.end ())
{
(this->*(it1->second))(it.second.data ());
InsertParam (results, it.first, "");
}
else
LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first);
}
}
void I2PControlService::PasswordHandler (const std::string& value)
{
LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!");
m_Password = value;
m_Tokens.clear ();
}
// RouterInfo
void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
{
for (auto it = params.begin (); it != params.end (); it++)
{
LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first);
auto it1 = m_RouterInfoHandlers.find (it->first);
if (it1 != m_RouterInfoHandlers.end ())
{
if (it != params.begin ()) results << ",";
(this->*(it1->second))(results);
}
else
LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first);
}
}
void I2PControlService::UptimeHandler (std::ostringstream& results)
{
InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000);
}
void I2PControlService::VersionHandler (std::ostringstream& results)
{
InsertParam (results, "i2p.router.version", VERSION);
}
void I2PControlService::StatusHandler (std::ostringstream& results)
{
InsertParam (results, "i2p.router.status", "???"); // TODO:
}
void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results)
{
InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ());
}
void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results)
{
InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ());
}
void I2PControlService::NetStatusHandler (std::ostringstream& results)
{
InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ());
}
void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results)
{
int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size ();
InsertParam (results, "i2p.router.net.tunnels.participating", transit);
}
void I2PControlService::InboundBandwidth1S (std::ostringstream& results)
{
double bw = i2p::transport::transports.GetInBandwidth ();
InsertParam (results, "i2p.router.net.bw.inbound.1s", bw);
}
void I2PControlService::OutboundBandwidth1S (std::ostringstream& results)
{
double bw = i2p::transport::transports.GetOutBandwidth ();
InsertParam (results, "i2p.router.net.bw.outbound.1s", bw);
}
void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results)
{
InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ());
}
void I2PControlService::NetTotalSentBytes (std::ostringstream& results)
{
InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ());
}
// RouterManager
void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
{
for (auto it = params.begin (); it != params.end (); it++)
{
if (it != params.begin ()) results << ",";
LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first);
auto it1 = m_RouterManagerHandlers.find (it->first);
if (it1 != m_RouterManagerHandlers.end ()) {
(this->*(it1->second))(results);
} else
LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first);
}
}
void I2PControlService::ShutdownHandler (std::ostringstream& results)
{
LogPrint (eLogInfo, "I2PControl: Shutdown requested");
InsertParam (results, "Shutdown", "");
m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent
m_ShutdownTimer.async_wait (
[](const boost::system::error_code& ecode)
{
Daemon.running = 0;
});
}
void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results)
{
i2p::context.SetAcceptsTunnels (false);
int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout ();
LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains");
InsertParam (results, "ShutdownGraceful", "");
m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second
m_ShutdownTimer.async_wait (
[](const boost::system::error_code& ecode)
{
Daemon.running = 0;
});
}
void I2PControlService::ReseedHandler (std::ostringstream& results)
{
LogPrint (eLogInfo, "I2PControl: Reseed requested");
InsertParam (results, "Reseed", "");
i2p::data::netdb.Reseed ();
}
// network setting
void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
{
for (auto it = params.begin (); it != params.end (); it++)
{
if (it != params.begin ()) results << ",";
LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first);
auto it1 = m_NetworkSettingHandlers.find (it->first);
if (it1 != m_NetworkSettingHandlers.end ()) {
(this->*(it1->second))(it->second.data (), results);
} else
LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first);
}
}
void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results)
{
if (value != "null")
i2p::context.SetBandwidth (std::atoi(value.c_str()));
int bw = i2p::context.GetBandwidthLimit();
InsertParam (results, "i2p.router.net.bw.in", bw);
}
void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results)
{
if (value != "null")
i2p::context.SetBandwidth (std::atoi(value.c_str()));
int bw = i2p::context.GetBandwidthLimit();
InsertParam (results, "i2p.router.net.bw.out", bw);
}
// certificate
void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path)
{
FILE *f = NULL;
EVP_PKEY * pkey = EVP_PKEY_new ();
RSA * rsa = RSA_new ();
BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ());
RSA_generate_key_ex (rsa, 4096, e, NULL);
BN_free (e);
if (rsa)
{
EVP_PKEY_assign_RSA (pkey, rsa);
X509 * x509 = X509_new ();
ASN1_INTEGER_set (X509_get_serialNumber (x509), 1);
X509_gmtime_adj (X509_get_notBefore (x509), 0);
X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration
X509_set_pubkey (x509, pkey); // public key
X509_NAME * name = X509_get_subject_name (x509);
X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"RU", -1, -1, 0); // country (Russia by default)
X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization
X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name
X509_set_issuer_name (x509, name); // set issuer to ourselves
X509_sign (x509, pkey, EVP_sha1 ()); // sign
// save cert
if ((f = fopen (crt_path, "wb")) != NULL) {
LogPrint (eLogInfo, "I2PControl: saving new cert to ", crt_path);
PEM_write_X509 (f, x509);
fclose (f);
} else {
LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno));
}
// save key
if ((f = fopen (key_path, "wb")) != NULL) {
LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path);
PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL);
fclose (f);
} else {
LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno));
}
X509_free (x509);
} else {
LogPrint (eLogError, "I2PControl: can't create RSA key for certificate");
}
EVP_PKEY_free (pkey);
}
}
}

View File

@@ -1,123 +0,0 @@
#ifndef I2P_CONTROL_H__
#define I2P_CONTROL_H__
#include <inttypes.h>
#include <thread>
#include <memory>
#include <array>
#include <string>
#include <sstream>
#include <map>
#include <set>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/property_tree/ptree.hpp>
namespace i2p
{
namespace client
{
const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024;
typedef std::array<char, I2P_CONTROL_MAX_REQUEST_SIZE> I2PControlBuffer;
const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years
const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol";
const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P";
class I2PControlService
{
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
public:
I2PControlService (const std::string& address, int port);
~I2PControlService ();
void Start ();
void Stop ();
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
void Handshake (std::shared_ptr<ssl_socket> socket);
void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
void ReadRequest (std::shared_ptr<ssl_socket> socket);
void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
void BuildErrorResponse (std::string & content, int code, const char *message);
void SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::string& response, bool isHtml);
void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
void CreateCertificate (const char *crt_path, const char *key_path);
private:
void InsertParam (std::ostringstream& ss, const std::string& name, int value) const;
void InsertParam (std::ostringstream& ss, const std::string& name, double value) const;
void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const;
// methods
typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results);
void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
// I2PControl
typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value);
void PasswordHandler (const std::string& value);
// RouterInfo
typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results);
void UptimeHandler (std::ostringstream& results);
void VersionHandler (std::ostringstream& results);
void StatusHandler (std::ostringstream& results);
void NetDbKnownPeersHandler (std::ostringstream& results);
void NetDbActivePeersHandler (std::ostringstream& results);
void NetStatusHandler (std::ostringstream& results);
void TunnelsParticipatingHandler (std::ostringstream& results);
void InboundBandwidth1S (std::ostringstream& results);
void OutboundBandwidth1S (std::ostringstream& results);
void NetTotalReceivedBytes (std::ostringstream& results);
void NetTotalSentBytes (std::ostringstream& results);
// RouterManager
typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results);
void ShutdownHandler (std::ostringstream& results);
void ShutdownGracefulHandler (std::ostringstream& results);
void ReseedHandler (std::ostringstream& results);
// NetworkSetting
typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results);
void InboundBandwidthLimit (const std::string& value, std::ostringstream& results);
void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results);
private:
std::string m_Password;
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ssl::context m_SSLContext;
boost::asio::deadline_timer m_ShutdownTimer;
std::set<std::string> m_Tokens;
std::map<std::string, MethodHandler> m_MethodHandlers;
std::map<std::string, I2PControlRequestHandler> m_I2PControlHandlers;
std::map<std::string, RouterInfoRequestHandler> m_RouterInfoHandlers;
std::map<std::string, RouterManagerRequestHandler> m_RouterManagerHandlers;
std::map<std::string, NetworkSettingRequestHandler> m_NetworkSettingHandlers;
};
}
}
#endif

View File

@@ -1,83 +0,0 @@
#include "I2PEndian.h"
// http://habrahabr.ru/post/121811/
// http://codepad.org/2ycmkz2y
#include "LittleBigEndian.h"
#ifdef NEEDS_LOCAL_ENDIAN
uint16_t htobe16(uint16_t int16)
{
BigEndian<uint16_t> u16(int16);
return u16.raw_value;
}
uint32_t htobe32(uint32_t int32)
{
BigEndian<uint32_t> u32(int32);
return u32.raw_value;
}
uint64_t htobe64(uint64_t int64)
{
BigEndian<uint64_t> u64(int64);
return u64.raw_value;
}
uint16_t be16toh(uint16_t big16)
{
LittleEndian<uint16_t> u16(big16);
return u16.raw_value;
}
uint32_t be32toh(uint32_t big32)
{
LittleEndian<uint32_t> u32(big32);
return u32.raw_value;
}
uint64_t be64toh(uint64_t big64)
{
LittleEndian<uint64_t> u64(big64);
return u64.raw_value;
}
#endif
/* it can be used in Windows 8
#include <Winsock2.h>
uint16_t htobe16(uint16_t int16)
{
return htons(int16);
}
uint32_t htobe32(uint32_t int32)
{
return htonl(int32);
}
uint64_t htobe64(uint64_t int64)
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
//return htonll(int64);
return 0;
}
uint16_t be16toh(uint16_t big16)
{
return ntohs(big16);
}
uint32_t be32toh(uint32_t big32)
{
return ntohl(big32);
}
uint64_t be64toh(uint64_t big64)
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
//return ntohll(big64);
return 0;
}
*/

View File

@@ -1,209 +0,0 @@
#include "Destination.h"
#include "Identity.h"
#include "ClientContext.h"
#include "I2PService.h"
namespace i2p
{
namespace client
{
static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256;
I2PService::I2PService (std::shared_ptr<ClientDestination> localDestination):
m_LocalDestination (localDestination ? localDestination :
i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE))
{
}
I2PService::I2PService (i2p::data::SigningKeyType kt):
m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt))
{
}
void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) {
assert(streamRequestComplete);
i2p::data::IdentHash identHash;
if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash))
m_LocalDestination->CreateStream (streamRequestComplete, identHash, port);
else
{
LogPrint (eLogWarning, "I2PService: Remote destination not found: ", dest);
streamRequestComplete (nullptr);
}
}
TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) {}
TCPIPPipe::~TCPIPPipe()
{
Terminate();
}
void TCPIPPipe::Start()
{
AsyncReceiveUpstream();
AsyncReceiveDownstream();
}
void TCPIPPipe::Terminate()
{
if(Kill()) return;
Done(shared_from_this());
if (m_up) {
if (m_up->is_open()) {
m_up->close();
}
m_up = nullptr;
}
if (m_down) {
if (m_down->is_open()) {
m_down->close();
}
m_down = nullptr;
}
}
void TCPIPPipe::AsyncReceiveUpstream()
{
if (m_up) {
m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE),
std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError, "TCPIPPipe: upstream receive: no socket");
}
}
void TCPIPPipe::AsyncReceiveDownstream()
{
if (m_down) {
m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE),
std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError, "TCPIPPipe: downstream receive: no socket");
}
}
void TCPIPPipe::UpstreamWrite(const uint8_t * buf, size_t len)
{
if (m_up) {
LogPrint(eLogDebug, "TCPIPPipe: upstream: ", (int) len, " bytes written");
boost::asio::async_write(*m_up, boost::asio::buffer(buf, len),
boost::asio::transfer_all(),
std::bind(&TCPIPPipe::HandleUpstreamWrite,
shared_from_this(),
std::placeholders::_1)
);
} else {
LogPrint(eLogError, "TCPIPPipe: upstream write: no socket");
}
}
void TCPIPPipe::DownstreamWrite(const uint8_t * buf, size_t len)
{
if (m_down) {
LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) len, " bytes written");
boost::asio::async_write(*m_down, boost::asio::buffer(buf, len),
boost::asio::transfer_all(),
std::bind(&TCPIPPipe::HandleDownstreamWrite,
shared_from_this(),
std::placeholders::_1)
);
} else {
LogPrint(eLogError, "TCPIPPipe: downstream write: no socket");
}
}
void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
{
LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) bytes_transfered, " bytes received");
if (ecode) {
LogPrint(eLogError, "TCPIPPipe: downstream read error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
} else {
if (bytes_transfered > 0 ) {
memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered);
UpstreamWrite(m_upstream_buf, bytes_transfered);
}
AsyncReceiveDownstream();
}
}
void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) {
if (ecode) {
LogPrint(eLogError, "TCPIPPipe: downstream write error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
}
}
void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) {
if (ecode) {
LogPrint(eLogError, "TCPIPPipe: upstream write error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
}
}
void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
{
LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int)bytes_transfered, " bytes received");
if (ecode) {
LogPrint(eLogError, "TCPIPPipe: upstream read error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
} else {
if (bytes_transfered > 0 ) {
memcpy(m_upstream_buf, m_upstream_to_down_buf, bytes_transfered);
DownstreamWrite(m_upstream_buf, bytes_transfered);
}
AsyncReceiveUpstream();
}
}
void TCPIPAcceptor::Start ()
{
m_Acceptor.listen ();
Accept ();
}
void TCPIPAcceptor::Stop ()
{
m_Acceptor.close();
m_Timer.cancel ();
ClearHandlers();
}
void TCPIPAcceptor::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
m_Acceptor.async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
if (!ecode)
{
LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted");
auto handler = CreateHandler(socket);
if (handler)
{
AddHandler(handler);
handler->Handle();
}
else
socket->close();
Accept();
}
else
{
if (ecode != boost::asio::error::operation_aborted)
LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ());
}
}
}
}

View File

@@ -1,136 +0,0 @@
#ifndef I2PSERVICE_H__
#define I2PSERVICE_H__
#include <atomic>
#include <mutex>
#include <unordered_set>
#include <memory>
#include <boost/asio.hpp>
#include "Destination.h"
#include "Identity.h"
namespace i2p
{
namespace client
{
class I2PServiceHandler;
class I2PService
{
public:
I2PService (std::shared_ptr<ClientDestination> localDestination = nullptr);
I2PService (i2p::data::SigningKeyType kt);
virtual ~I2PService () { ClearHandlers (); }
inline void AddHandler (std::shared_ptr<I2PServiceHandler> conn)
{
std::unique_lock<std::mutex> l(m_HandlersMutex);
m_Handlers.insert(conn);
}
inline void RemoveHandler (std::shared_ptr<I2PServiceHandler> conn)
{
std::unique_lock<std::mutex> l(m_HandlersMutex);
m_Handlers.erase(conn);
}
inline void ClearHandlers ()
{
std::unique_lock<std::mutex> l(m_HandlersMutex);
m_Handlers.clear();
}
inline std::shared_ptr<ClientDestination> GetLocalDestination () { return m_LocalDestination; }
inline void SetLocalDestination (std::shared_ptr<ClientDestination> dest) { m_LocalDestination = dest; }
void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0);
inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }
virtual void Start () = 0;
virtual void Stop () = 0;
virtual const char* GetName() { return "Generic I2P Service"; }
private:
std::shared_ptr<ClientDestination> m_LocalDestination;
std::unordered_set<std::shared_ptr<I2PServiceHandler> > m_Handlers;
std::mutex m_HandlersMutex;
};
/*Simple interface for I2PHandlers, allows detection of finalization amongst other things */
class I2PServiceHandler
{
public:
I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { }
virtual ~I2PServiceHandler() { }
//If you override this make sure you call it from the children
virtual void Handle() {}; //Start handling the socket
protected:
// Call when terminating or handing over to avoid race conditions
inline bool Kill () { return m_Dead.exchange(true); }
// Call to know if the handler is dead
inline bool Dead () { return m_Dead; }
// Call when done to clean up (make sure Kill is called first)
inline void Done (std::shared_ptr<I2PServiceHandler> me) { if(m_Service) m_Service->RemoveHandler(me); }
// Call to talk with the owner
inline I2PService * GetOwner() { return m_Service; }
private:
I2PService *m_Service;
std::atomic<bool> m_Dead; //To avoid cleaning up multiple times
};
const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192;
// bidirectional pipe for 2 tcp/ip sockets
class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this<TCPIPPipe> {
public:
TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream);
~TCPIPPipe();
void Start();
protected:
void Terminate();
void AsyncReceiveUpstream();
void AsyncReceiveDownstream();
void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
void HandleUpstreamWrite(const boost::system::error_code & ecode);
void HandleDownstreamWrite(const boost::system::error_code & ecode);
void UpstreamWrite(const uint8_t * buf, size_t len);
void DownstreamWrite(const uint8_t * buf, size_t len);
private:
uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE];
uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE];
std::shared_ptr<boost::asio::ip::tcp::socket> m_up, m_down;
};
/* TODO: support IPv6 too */
//This is a service that listens for connections on the IP network and interacts with I2P
class TCPIPAcceptor: public I2PService
{
public:
TCPIPAcceptor (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination = nullptr) :
I2PService(localDestination),
m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
m_Timer (GetService ()) {}
TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) :
I2PService(kt),
m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
m_Timer (GetService ()) {}
virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); }
//If you override this make sure you call it from the children
void Start ();
//If you override this make sure you call it from the children
void Stop ();
const boost::asio::ip::tcp::acceptor& GetAcceptor () const { return m_Acceptor; };
protected:
virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket) = 0;
virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; }
private:
void Accept();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::deadline_timer m_Timer;
};
}
}
#endif

View File

@@ -1,503 +0,0 @@
#include <cassert>
#include "Base.h"
#include "Log.h"
#include "Destination.h"
#include "ClientContext.h"
#include "I2PTunnel.h"
namespace i2p
{
namespace client
{
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port):
I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()),
m_IsQuiet (true)
{
m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port);
}
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream):
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true)
{
}
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, const boost::asio::ip::tcp::endpoint& target, bool quiet):
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
m_RemoteEndpoint (target), m_IsQuiet (quiet)
{
}
I2PTunnelConnection::~I2PTunnelConnection ()
{
}
void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len)
{
if (m_Stream)
{
if (msg)
m_Stream->Send (msg, len); // connect and send
else
m_Stream->Send (m_Buffer, 0); // connect
}
StreamReceive ();
Receive ();
}
void I2PTunnelConnection::Connect ()
{
if (m_Socket)
m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect,
shared_from_this (), std::placeholders::_1));
}
void I2PTunnelConnection::Terminate ()
{
if (Kill()) return;
if (m_Stream)
{
m_Stream->Close ();
m_Stream.reset ();
}
m_Socket->close ();
Done(shared_from_this ());
}
void I2PTunnelConnection::Receive ()
{
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (m_Stream)
{
auto s = shared_from_this ();
m_Stream->AsyncSend (m_Buffer, bytes_transferred,
[s](const boost::system::error_code& ecode)
{
if (!ecode)
s->Receive ();
else
s->Terminate ();
});
}
}
}
void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
StreamReceive ();
}
void I2PTunnelConnection::StreamReceive ()
{
if (m_Stream)
{
if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew ||
m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular
{
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2),
I2P_TUNNEL_CONNECTION_MAX_IDLE);
}
else // closed by peer
{
// get remaning data
auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE);
if (len > 0) // still some data
Write (m_StreamBuffer, len);
else // no more data
Terminate ();
}
}
}
void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
{
if (bytes_transferred > 0)
Write (m_StreamBuffer, bytes_transferred); // postpone termination
else
Terminate ();
}
}
else
Write (m_StreamBuffer, bytes_transferred);
}
void I2PTunnelConnection::Write (const uint8_t * buf, size_t len)
{
boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (),
std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1));
}
void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint (eLogError, "I2PTunnel: connect error: ", ecode.message ());
Terminate ();
}
else
{
LogPrint (eLogDebug, "I2PTunnel: connected");
if (m_IsQuiet)
StreamReceive ();
else
{
// send destination first like received from I2P
std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 ();
dest += "\n";
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
HandleStreamReceive (boost::system::error_code (), dest.size ());
}
Receive ();
}
}
I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, const std::string& host):
I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false), m_From (stream->GetRemoteIdentity ())
{
}
void I2PTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len)
{
if (m_HeaderSent)
I2PTunnelConnection::Write (buf, len);
else
{
m_InHeader.clear ();
m_InHeader.write ((const char *)buf, len);
std::string line;
bool endOfHeader = false;
while (!endOfHeader)
{
std::getline(m_InHeader, line);
if (!m_InHeader.fail ())
{
if (line == "\r") endOfHeader = true;
else
{
if (line.find ("Host:") != std::string::npos)
m_OutHeader << "Host: " << m_Host << "\r\n";
else
m_OutHeader << line << "\n";
}
}
else
break;
}
// add X-I2P fields
if (m_From)
{
m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n";
m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n";
m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n";
}
if (endOfHeader)
{
m_OutHeader << "\r\n"; // end of header
m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header
m_HeaderSent = true;
I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ());
}
}
}
I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass):
I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()),
m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass)
{
}
void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len)
{
if (m_NeedsWebIrc) {
m_NeedsWebIrc = false;
m_OutPacket.str ("");
m_OutPacket << "WEBIRC " << this->m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " 127.0.0.1\n";
I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ());
}
std::string line;
m_OutPacket.str ("");
m_InPacket.clear ();
m_InPacket.write ((const char *)buf, len);
while (!m_InPacket.eof () && !m_InPacket.fail ())
{
std::getline (m_InPacket, line);
if (line.length () == 0 && m_InPacket.eof ()) {
m_InPacket.str ("");
}
auto pos = line.find ("USER");
if (pos != std::string::npos && pos == 0)
{
pos = line.find (" ");
pos++;
pos = line.find (" ", pos);
pos++;
auto nextpos = line.find (" ", pos);
m_OutPacket << line.substr (0, pos);
m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ());
m_OutPacket << line.substr (nextpos) << '\n';
} else {
m_OutPacket << line << '\n';
}
}
I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ());
}
/* This handler tries to stablish a connection with the desired server and dies if it fails to do so */
class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this<I2PClientTunnelHandler>
{
public:
I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination,
int destinationPort, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
I2PServiceHandler(parent), m_DestinationIdentHash(destination),
m_DestinationPort (destinationPort), m_Socket(socket) {};
void Handle();
void Terminate();
private:
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
i2p::data::IdentHash m_DestinationIdentHash;
int m_DestinationPort;
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
};
void I2PClientTunnelHandler::Handle()
{
GetOwner()->GetLocalDestination ()->CreateStream (
std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1),
m_DestinationIdentHash, m_DestinationPort);
}
void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
if (Kill()) return;
LogPrint (eLogDebug, "I2PTunnel: new connection");
auto connection = std::make_shared<I2PTunnelConnection>(GetOwner(), m_Socket, stream);
GetOwner()->AddHandler (connection);
connection->I2PConnect ();
Done(shared_from_this());
}
else
{
LogPrint (eLogError, "I2PTunnel: Client Tunnel Issue when creating the stream, check the previous warnings for more info.");
Terminate();
}
}
void I2PClientTunnelHandler::Terminate()
{
if (Kill()) return;
if (m_Socket)
{
m_Socket->close();
m_Socket = nullptr;
}
Done(shared_from_this());
}
I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination,
const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, int destinationPort):
TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination),
m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort)
{
}
void I2PClientTunnel::Start ()
{
TCPIPAcceptor::Start ();
GetIdentHash();
}
void I2PClientTunnel::Stop ()
{
TCPIPAcceptor::Stop();
auto *originalIdentHash = m_DestinationIdentHash;
m_DestinationIdentHash = nullptr;
delete originalIdentHash;
}
/* HACK: maybe we should create a caching IdentHash provider in AddressBook */
const i2p::data::IdentHash * I2PClientTunnel::GetIdentHash ()
{
if (!m_DestinationIdentHash)
{
i2p::data::IdentHash identHash;
if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash))
m_DestinationIdentHash = new i2p::data::IdentHash (identHash);
else
LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found");
}
return m_DestinationIdentHash;
}
std::shared_ptr<I2PServiceHandler> I2PClientTunnel::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
const i2p::data::IdentHash *identHash = GetIdentHash();
if (identHash)
return std::make_shared<I2PClientTunnelHandler>(this, *identHash, m_DestinationPort, socket);
else
return nullptr;
}
I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address,
int port, std::shared_ptr<ClientDestination> localDestination, int inport, bool gzip):
I2PService (localDestination), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false)
{
m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip);
}
void I2PServerTunnel::Start ()
{
m_Endpoint.port (m_Port);
boost::system::error_code ec;
auto addr = boost::asio::ip::address::from_string (m_Address, ec);
if (!ec)
{
m_Endpoint.address (addr);
Accept ();
}
else
{
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(GetService ());
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""),
std::bind (&I2PServerTunnel::HandleResolve, this,
std::placeholders::_1, std::placeholders::_2, resolver));
}
}
void I2PServerTunnel::Stop ()
{
ClearHandlers ();
}
void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
{
if (!ecode)
{
auto addr = (*it).endpoint ().address ();
LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr);
m_Endpoint.address (addr);
Accept ();
}
else
LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ());
}
void I2PServerTunnel::SetAccessList (const std::set<i2p::data::IdentHash>& accessList)
{
m_AccessList = accessList;
m_IsAccessList = true;
}
void I2PServerTunnel::Accept ()
{
if (m_PortDestination)
m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
auto localDestination = GetLocalDestination ();
if (localDestination)
{
if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet
localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
}
else
LogPrint (eLogError, "I2PTunnel: Local destination not set for server tunnel");
}
void I2PServerTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
if (m_IsAccessList)
{
if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ()))
{
LogPrint (eLogWarning, "I2PTunnel: Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped");
stream->Close ();
return;
}
}
CreateI2PConnection (stream);
}
}
void I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint ());
AddHandler (conn);
conn->Connect ();
}
I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address,
int port, std::shared_ptr<ClientDestination> localDestination,
const std::string& host, int inport, bool gzip):
I2PServerTunnel (name, address, port, localDestination, inport, gzip),
m_Host (host.length () > 0 ? host : address)
{
}
void I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{
auto conn = std::make_shared<I2PTunnelConnectionHTTP> (this, stream,
std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), m_Host);
AddHandler (conn);
conn->Connect ();
}
I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address,
int port, std::shared_ptr<ClientDestination> localDestination,
const std::string& webircpass, int inport, bool gzip):
I2PServerTunnel (name, address, port, localDestination, inport, gzip),
m_WebircPass (webircpass)
{
}
void I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{
auto conn = std::make_shared<I2PTunnelConnectionIRC> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), this->m_WebircPass);
AddHandler (conn);
conn->Connect ();
}
}
}

View File

@@ -1,208 +0,0 @@
#ifndef I2PTUNNEL_H__
#define I2PTUNNEL_H__
#include <inttypes.h>
#include <string>
#include <set>
#include <memory>
#include <sstream>
#include <boost/asio.hpp>
#include "Identity.h"
#include "Destination.h"
#include "Streaming.h"
#include "I2PService.h"
namespace i2p
{
namespace client
{
const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192;
const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds
const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
// for HTTP tunnels
const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64
const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64
const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address
class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this<I2PTunnelConnection>
{
public:
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port = 0); // to I2P
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API
I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P
~I2PTunnelConnection ();
void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0);
void Connect ();
protected:
void Terminate ();
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
virtual void Write (const uint8_t * buf, size_t len); // can be overloaded
void HandleWrite (const boost::system::error_code& ecode);
void StreamReceive ();
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleConnect (const boost::system::error_code& ecode);
private:
uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE];
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
std::shared_ptr<i2p::stream::Stream> m_Stream;
boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
bool m_IsQuiet; // don't send destination
};
class I2PTunnelConnectionHTTP: public I2PTunnelConnection
{
public:
I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, const std::string& host);
protected:
void Write (const uint8_t * buf, size_t len);
private:
std::string m_Host;
std::stringstream m_InHeader, m_OutHeader;
bool m_HeaderSent;
std::shared_ptr<const i2p::data::IdentityEx> m_From;
};
class I2PTunnelConnectionIRC: public I2PTunnelConnection
{
public:
I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass);
protected:
void Write (const uint8_t * buf, size_t len);
private:
std::shared_ptr<const i2p::data::IdentityEx> m_From;
std::stringstream m_OutPacket, m_InPacket;
bool m_NeedsWebIrc;
std::string m_WebircPass;
};
class I2PClientTunnel: public TCPIPAcceptor
{
protected:
// Implements TCPIPAcceptor
std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
public:
I2PClientTunnel (const std::string& name, const std::string& destination,
const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, int destinationPort = 0);
~I2PClientTunnel () {}
void Start ();
void Stop ();
const char* GetName() { return m_Name.c_str (); }
private:
const i2p::data::IdentHash * GetIdentHash ();
private:
std::string m_Name, m_Destination;
const i2p::data::IdentHash * m_DestinationIdentHash;
int m_DestinationPort;
};
class I2PServerTunnel: public I2PService
{
public:
I2PServerTunnel (const std::string& name, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int inport = 0, bool gzip = true);
void Start ();
void Stop ();
void SetAccessList (const std::set<i2p::data::IdentHash>& accessList);
const std::string& GetAddress() const { return m_Address; }
int GetPort () const { return m_Port; };
uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); };
const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; }
const char* GetName() { return m_Name.c_str (); }
private:
void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
void Accept ();
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
virtual void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
private:
std::string m_Name, m_Address;
int m_Port;
boost::asio::ip::tcp::endpoint m_Endpoint;
std::shared_ptr<i2p::stream::StreamingDestination> m_PortDestination;
std::set<i2p::data::IdentHash> m_AccessList;
bool m_IsAccessList;
};
class I2PServerTunnelHTTP: public I2PServerTunnel
{
public:
I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, const std::string& host,
int inport = 0, bool gzip = true);
private:
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
private:
std::string m_Host;
};
class I2PServerTunnelIRC: public I2PServerTunnel
{
public:
I2PServerTunnelIRC (const std::string& name, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, const std::string& webircpass,
int inport = 0, bool gzip = true);
private:
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
private:
std::string m_WebircPass;
};
}
}
#endif

View File

@@ -1,577 +0,0 @@
#include <time.h>
#include <stdio.h>
#include "Crypto.h"
#include "I2PEndian.h"
#include "Log.h"
#include "Identity.h"
namespace i2p
{
namespace data
{
Identity& Identity::operator=(const Keys& keys)
{
// copy public and signing keys together
memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey));
memset (certificate, 0, sizeof (certificate));
return *this;
}
size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
{
if ( len < DEFAULT_IDENTITY_SIZE ) {
// buffer too small, don't overflow
return 0;
}
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
return DEFAULT_IDENTITY_SIZE;
}
IdentHash Identity::Hash () const
{
IdentHash hash;
SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
return hash;
}
IdentityEx::IdentityEx ():
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
{
}
IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type)
{
memcpy (m_StandardIdentity.publicKey, publicKey, sizeof (m_StandardIdentity.publicKey));
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
{
size_t excessLen = 0;
uint8_t * excessBuf = nullptr;
switch (type)
{
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
{
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
{
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP384_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512 - 128
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 128, excessLen);
break;
}
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
{
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH);
break;
}
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
}
m_ExtendedLen = 4 + excessLen; // 4 bytes extra + excess length
// fill certificate
m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY;
htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen);
// fill extended buffer
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
htobe16buf (m_ExtendedBuffer, type);
htobe16buf (m_ExtendedBuffer + 2, CRYPTO_KEY_TYPE_ELGAMAL);
if (excessLen && excessBuf)
{
memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
delete[] excessBuf;
}
// calculate ident hash
uint8_t * buf = new uint8_t[GetFullLen ()];
ToBuffer (buf, GetFullLen ());
SHA256(buf, GetFullLen (), m_IdentHash);
delete[] buf;
}
else // DSA-SHA1
{
memcpy (m_StandardIdentity.signingKey, signingKey, sizeof (m_StandardIdentity.signingKey));
memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate));
m_IdentHash = m_StandardIdentity.Hash ();
m_ExtendedLen = 0;
m_ExtendedBuffer = nullptr;
}
CreateVerifier ();
}
IdentityEx::IdentityEx (const uint8_t * buf, size_t len):
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
{
FromBuffer (buf, len);
}
IdentityEx::IdentityEx (const IdentityEx& other):
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
{
*this = other;
}
IdentityEx::IdentityEx (const Identity& standard):
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
{
*this = standard;
}
IdentityEx::~IdentityEx ()
{
delete[] m_ExtendedBuffer;
}
IdentityEx& IdentityEx::operator=(const IdentityEx& other)
{
memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
m_IdentHash = other.m_IdentHash;
delete[] m_ExtendedBuffer;
m_ExtendedLen = other.m_ExtendedLen;
if (m_ExtendedLen > 0)
{
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
}
else
m_ExtendedBuffer = nullptr;
m_Verifier = nullptr;
return *this;
}
IdentityEx& IdentityEx::operator=(const Identity& standard)
{
m_StandardIdentity = standard;
m_IdentHash = m_StandardIdentity.Hash ();
delete[] m_ExtendedBuffer;
m_ExtendedBuffer = nullptr;
m_ExtendedLen = 0;
m_Verifier = nullptr;
return *this;
}
size_t IdentityEx::FromBuffer (const uint8_t * buf, size_t len)
{
if (len < DEFAULT_IDENTITY_SIZE)
{
LogPrint (eLogError, "Identity: buffer length ", len, " is too small");
return 0;
}
memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
delete[] m_ExtendedBuffer; m_ExtendedBuffer = nullptr;
m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1);
if (m_ExtendedLen)
{
if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len)
{
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
}
else
{
LogPrint (eLogError, "Identity: Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE);
m_ExtendedLen = 0;
return 0;
}
}
else
{
m_ExtendedLen = 0;
m_ExtendedBuffer = nullptr;
}
SHA256(buf, GetFullLen (), m_IdentHash);
m_Verifier = nullptr;
return GetFullLen ();
}
size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const
{
const size_t fullLen = GetFullLen();
if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else
memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
if (m_ExtendedLen > 0 && m_ExtendedBuffer)
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
return fullLen;
}
size_t IdentityEx::FromBase64(const std::string& s)
{
const size_t slen = s.length();
std::vector<uint8_t> buf(slen); // binary data can't exceed base64
const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen);
return FromBuffer (buf.data(), len);
}
std::string IdentityEx::ToBase64 () const
{
const size_t bufLen = GetFullLen();
const size_t strLen = Base64EncodingBufferSize(bufLen);
std::vector<uint8_t> buf(bufLen);
std::vector<char> str(strLen);
size_t l = ToBuffer (buf.data(), bufLen);
size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen);
return std::string (str.data(), l1);
}
size_t IdentityEx::GetSigningPublicKeyLen () const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->GetPublicKeyLen ();
return 128;
}
size_t IdentityEx::GetSigningPrivateKeyLen () const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->GetPrivateKeyLen ();
return GetSignatureLen ()/2;
}
size_t IdentityEx::GetSignatureLen () const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->GetSignatureLen ();
return i2p::crypto::DSA_SIGNATURE_LENGTH;
}
bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
if (!m_Verifier) CreateVerifier ();
if (m_Verifier)
return m_Verifier->Verify (buf, len, signature);
return false;
}
SigningKeyType IdentityEx::GetSigningKeyType () const
{
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
return bufbe16toh (m_ExtendedBuffer); // signing key
return SIGNING_KEY_TYPE_DSA_SHA1;
}
CryptoKeyType IdentityEx::GetCryptoKeyType () const
{
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
return bufbe16toh (m_ExtendedBuffer + 2); // crypto key
return CRYPTO_KEY_TYPE_ELGAMAL;
}
void IdentityEx::CreateVerifier () const
{
auto keyType = GetSigningKeyType ();
switch (keyType)
{
case SIGNING_KEY_TYPE_DSA_SHA1:
UpdateVerifier (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey));
break;
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
{
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
UpdateVerifier (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding));
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
{
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
UpdateVerifier (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding));
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
{
uint8_t signingKey[i2p::crypto::ECDSAP521_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
UpdateVerifier (new i2p::crypto::ECDSAP521Verifier (signingKey));
break;
}
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
{
uint8_t signingKey[i2p::crypto::RSASHA2562048_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
UpdateVerifier (new i2p::crypto:: RSASHA2562048Verifier (signingKey));
break;
}
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
{
uint8_t signingKey[i2p::crypto::RSASHA3843072_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
UpdateVerifier (new i2p::crypto:: RSASHA3843072Verifier (signingKey));
break;
}
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
{
uint8_t signingKey[i2p::crypto::RSASHA5124096_KEY_LENGTH];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
UpdateVerifier (new i2p::crypto:: RSASHA5124096Verifier (signingKey));
break;
}
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
{
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
UpdateVerifier (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding));
break;
}
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
}
}
void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const
{
if (!m_Verifier || !verifier)
m_Verifier.reset (verifier);
else
delete verifier;
}
void IdentityEx::DropVerifier () const
{
// TODO: potential race condition with Verify
m_Verifier = nullptr;
}
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
{
m_Public = std::make_shared<IdentityEx>(Identity (keys));
memcpy (m_PrivateKey, keys.privateKey, 256); // 256
memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ());
m_Signer = nullptr;
CreateSigner ();
return *this;
}
PrivateKeys& PrivateKeys::operator=(const PrivateKeys& other)
{
m_Public = std::make_shared<IdentityEx>(*other.m_Public);
memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_Public->GetSigningPrivateKeyLen ());
m_Signer = nullptr;
CreateSigner ();
return *this;
}
size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len)
{
m_Public = std::make_shared<IdentityEx>(buf, len);
size_t ret = m_Public->GetFullLen ();
memcpy (m_PrivateKey, buf + ret, 256); // private key always 256
ret += 256;
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
ret += signingPrivateKeySize;
m_Signer = nullptr;
CreateSigner ();
return ret;
}
size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const
{
size_t ret = m_Public->ToBuffer (buf, len);
memcpy (buf + ret, m_PrivateKey, 256); // private key always 256
ret += 256;
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
ret += signingPrivateKeySize;
return ret;
}
size_t PrivateKeys::FromBase64(const std::string& s)
{
uint8_t * buf = new uint8_t[s.length ()];
size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
size_t ret = FromBuffer (buf, l);
delete[] buf;
return ret;
}
std::string PrivateKeys::ToBase64 () const
{
uint8_t * buf = new uint8_t[GetFullLen ()];
char * str = new char[GetFullLen ()*2];
size_t l = ToBuffer (buf, GetFullLen ());
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2);
str[l1] = 0;
delete[] buf;
std::string ret(str);
delete[] str;
return ret;
}
void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
if (m_Signer)
m_Signer->Sign (buf, len, signature);
}
void PrivateKeys::CreateSigner ()
{
switch (m_Public->GetSigningKeyType ())
{
case SIGNING_KEY_TYPE_DSA_SHA1:
m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
m_Signer.reset (new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
m_Signer.reset (new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
m_Signer.reset (new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
m_Signer.reset (new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
m_Signer.reset (new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
m_Signer.reset (new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey));
break;
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey));
break;
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported");
}
}
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type)
{
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
{
PrivateKeys keys;
// signature
uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
switch (type)
{
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
i2p::crypto::CreateECDSAP256RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
i2p::crypto::CreateECDSAP384RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
i2p::crypto::CreateECDSAP521RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
break;
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
break;
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
}
// encryption
uint8_t publicKey[256];
i2p::crypto::GenerateElGamalKeyPair (keys.m_PrivateKey, publicKey);
// identity
keys.m_Public = std::make_shared<IdentityEx> (publicKey, signingPublicKey, type);
keys.CreateSigner ();
return keys;
}
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
}
Keys CreateRandomKeys ()
{
Keys keys;
// encryption
i2p::crypto::GenerateElGamalKeyPair(keys.privateKey, keys.publicKey);
// signing
i2p::crypto::CreateDSARandomKeys (keys.signingPrivateKey, keys.signingKey);
return keys;
}
IdentHash CreateRoutingKey (const IdentHash& ident)
{
uint8_t buf[41]; // ident + yyyymmdd
memcpy (buf, (const uint8_t *)ident, 32);
time_t t = time (nullptr);
struct tm tm;
#ifdef _WIN32
gmtime_s(&tm, &t);
sprintf_s((char *)(buf + 32), 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
#else
gmtime_r(&t, &tm);
sprintf((char *)(buf + 32), "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
#endif
IdentHash key;
SHA256(buf, 40, key);
return key;
}
XORMetric operator^(const IdentHash& key1, const IdentHash& key2)
{
XORMetric m;
const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL ();
m.metric_ll[0] = hash1[0] ^ hash2[0];
m.metric_ll[1] = hash1[1] ^ hash2[1];
m.metric_ll[2] = hash1[2] ^ hash2[2];
m.metric_ll[3] = hash1[3] ^ hash2[3];
return m;
}
}
}

View File

@@ -1,190 +0,0 @@
#ifndef IDENTITY_H__
#define IDENTITY_H__
#include <inttypes.h>
#include <string.h>
#include <string>
#include <memory>
#include "Base.h"
#include "Signature.h"
namespace i2p
{
namespace data
{
typedef Tag<32> IdentHash;
inline std::string GetIdentHashAbbreviation (const IdentHash& ident)
{
return ident.ToBase64 ().substr (0, 4);
}
struct Keys
{
uint8_t privateKey[256];
uint8_t signingPrivateKey[20];
uint8_t publicKey[256];
uint8_t signingKey[128];
};
const uint8_t CERTIFICATE_TYPE_NULL = 0;
const uint8_t CERTIFICATE_TYPE_HASHCASH = 1;
const uint8_t CERTIFICATE_TYPE_HIDDEN = 2;
const uint8_t CERTIFICATE_TYPE_SIGNED = 3;
const uint8_t CERTIFICATE_TYPE_MULTIPLE = 4;
const uint8_t CERTIFICATE_TYPE_KEY = 5;
struct Identity
{
uint8_t publicKey[256];
uint8_t signingKey[128];
uint8_t certificate[3]; // byte 1 - type, bytes 2-3 - length
Identity () = default;
Identity (const Keys& keys) { *this = keys; };
Identity& operator=(const Keys& keys);
size_t FromBuffer (const uint8_t * buf, size_t len);
IdentHash Hash () const;
};
Keys CreateRandomKeys ();
const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA512_P521 = 3;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA256_2048 = 4;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6;
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7;
typedef uint16_t SigningKeyType;
typedef uint16_t CryptoKeyType;
class IdentityEx
{
public:
IdentityEx ();
IdentityEx (const uint8_t * publicKey, const uint8_t * signingKey,
SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
IdentityEx (const uint8_t * buf, size_t len);
IdentityEx (const IdentityEx& other);
IdentityEx (const Identity& standard);
~IdentityEx ();
IdentityEx& operator=(const IdentityEx& other);
IdentityEx& operator=(const Identity& standard);
size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(const std::string& s);
std::string ToBase64 () const;
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
const IdentHash& GetIdentHash () const { return m_IdentHash; };
const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; };
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
size_t GetSigningPublicKeyLen () const;
size_t GetSigningPrivateKeyLen () const;
size_t GetSignatureLen () const;
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
SigningKeyType GetSigningKeyType () const;
CryptoKeyType GetCryptoKeyType () const;
void DropVerifier () const; // to save memory
private:
void CreateVerifier () const;
void UpdateVerifier (i2p::crypto::Verifier * verifier) const;
private:
Identity m_StandardIdentity;
IdentHash m_IdentHash;
mutable std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
size_t m_ExtendedLen;
uint8_t * m_ExtendedBuffer;
};
class PrivateKeys // for eepsites
{
public:
PrivateKeys () = default;
PrivateKeys (const PrivateKeys& other) { *this = other; };
PrivateKeys (const Keys& keys) { *this = keys; };
PrivateKeys& operator=(const Keys& keys);
PrivateKeys& operator=(const PrivateKeys& other);
~PrivateKeys () = default;
std::shared_ptr<const IdentityEx> GetPublic () const { return m_Public; };
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); };
size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(const std::string& s);
std::string ToBase64 () const;
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
private:
void CreateSigner ();
private:
std::shared_ptr<IdentityEx> m_Public;
uint8_t m_PrivateKey[256];
uint8_t m_SigningPrivateKey[1024]; // assume private key doesn't exceed 1024 bytes
std::unique_ptr<i2p::crypto::Signer> m_Signer;
};
// kademlia
struct XORMetric
{
union
{
uint8_t metric[32];
uint64_t metric_ll[4];
};
void SetMin () { memset (metric, 0, 32); };
void SetMax () { memset (metric, 0xFF, 32); };
bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; };
};
IdentHash CreateRoutingKey (const IdentHash& ident);
XORMetric operator^(const IdentHash& key1, const IdentHash& key2);
// destination for delivery instuctions
class RoutingDestination
{
public:
RoutingDestination () {};
virtual ~RoutingDestination () {};
virtual const IdentHash& GetIdentHash () const = 0;
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
virtual bool IsDestination () const = 0; // for garlic
};
class LocalDestination
{
public:
virtual ~LocalDestination() {};
virtual const uint8_t * GetEncryptionPrivateKey () const = 0;
virtual std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
};
}
}
#endif

View File

@@ -1,249 +0,0 @@
#include <string.h>
#include "I2PEndian.h"
#include "Crypto.h"
#include "Log.h"
#include "Timestamp.h"
#include "NetDb.h"
#include "Tunnel.h"
#include "LeaseSet.h"
namespace i2p
{
namespace data
{
LeaseSet::LeaseSet (const uint8_t * buf, size_t len, bool storeLeases):
m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0)
{
m_Buffer = new uint8_t[len];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer ();
}
void LeaseSet::Update (const uint8_t * buf, size_t len)
{
if (len > m_BufferLen)
{
auto oldBuffer = m_Buffer;
m_Buffer = new uint8_t[len];
delete[] oldBuffer;
}
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer (false);
}
void LeaseSet::PopulateLeases ()
{
m_StoreLeases = true;
ReadFromBuffer (false);
}
void LeaseSet::ReadFromBuffer (bool readIdentity)
{
if (readIdentity || !m_Identity)
m_Identity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen);
size_t size = m_Identity->GetFullLen ();
if (size > m_BufferLen)
{
LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", m_BufferLen);
m_IsValid = false;
return;
}
memcpy (m_EncryptionKey, m_Buffer + size, 256);
size += 256; // encryption key
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
uint8_t num = m_Buffer[size];
size++; // num
LogPrint (eLogDebug, "LeaseSet: read num=", (int)num);
if (!num || num > MAX_NUM_LEASES)
{
LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)num);
m_IsValid = false;
return;
}
// reset existing leases
if (m_StoreLeases)
for (auto it: m_Leases)
it->isUpdated = false;
else
m_Leases.clear ();
// process leases
m_ExpirationTime = 0;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
const uint8_t * leases = m_Buffer + size;
for (int i = 0; i < num; i++)
{
Lease lease;
lease.tunnelGateway = leases;
leases += 32; // gateway
lease.tunnelID = bufbe32toh (leases);
leases += 4; // tunnel ID
lease.endDate = bufbe64toh (leases);
leases += 8; // end date
if (ts < lease.endDate + LEASE_ENDDATE_THRESHOLD)
{
if (lease.endDate > m_ExpirationTime)
m_ExpirationTime = lease.endDate;
if (m_StoreLeases)
{
auto ret = m_Leases.insert (std::make_shared<Lease>(lease));
if (!ret.second) *(*ret.first) = lease; // update existing
(*ret.first)->isUpdated = true;
// check if lease's gateway is in our netDb
if (!netdb.FindRouter (lease.tunnelGateway))
{
// if not found request it
LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting");
netdb.RequestDestination (lease.tunnelGateway);
}
}
}
else
LogPrint (eLogWarning, "LeaseSet: Lease is expired already ");
}
if (!m_ExpirationTime)
{
LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped");
m_IsValid = false;
return;
}
m_ExpirationTime += LEASE_ENDDATE_THRESHOLD;
// delete old leases
if (m_StoreLeases)
{
for (auto it = m_Leases.begin (); it != m_Leases.end ();)
{
if (!(*it)->isUpdated)
{
(*it)->endDate = 0; // somebody might still hold it
m_Leases.erase (it++);
}
else
it++;
}
}
// verify
if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases))
{
LogPrint (eLogWarning, "LeaseSet: verification failed");
m_IsValid = false;
}
}
uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const
{
if (!m_Identity) return 0;
size_t size = m_Identity->GetFullLen ();
if (size > len) return 0;
size += 256; // encryption key
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
if (size > len) return 0;
uint8_t num = buf[size];
size++; // num
if (size + num*LEASE_SIZE > len) return 0;
uint64_t timestamp= 0 ;
for (int i = 0; i < num; i++)
{
size += 36; // gateway (32) + tunnelId(4)
auto endDate = bufbe64toh (buf + size);
size += 8; // end date
if (!timestamp || endDate < timestamp)
timestamp = endDate;
}
return timestamp;
}
bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const
{
return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen);
}
const std::vector<std::shared_ptr<const Lease> > LeaseSet::GetNonExpiredLeases (bool withThreshold) const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
std::vector<std::shared_ptr<const Lease> > leases;
for (auto it: m_Leases)
{
auto endDate = it->endDate;
if (withThreshold)
endDate += LEASE_ENDDATE_THRESHOLD;
else
endDate -= LEASE_ENDDATE_THRESHOLD;
if (ts < endDate)
leases.push_back (it);
}
return leases;
}
bool LeaseSet::HasExpiredLeases () const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: m_Leases)
if (ts >= it->endDate) return true;
return false;
}
bool LeaseSet::IsExpired () const
{
if (IsEmpty ()) return true;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
return ts > m_ExpirationTime;
}
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels):
m_ExpirationTime (0), m_Identity (identity)
{
int num = tunnels.size ();
if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
// identity
auto signingKeyLen = m_Identity->GetSigningPublicKeyLen ();
m_BufferLen = m_Identity->GetFullLen () + 256 + signingKeyLen + 1 + num*LEASE_SIZE + m_Identity->GetSignatureLen ();
m_Buffer = new uint8_t[m_BufferLen];
auto offset = m_Identity->ToBuffer (m_Buffer, m_BufferLen);
memcpy (m_Buffer + offset, encryptionPublicKey, 256);
offset += 256;
memset (m_Buffer + offset, 0, signingKeyLen);
offset += signingKeyLen;
// num leases
m_Buffer[offset] = num;
offset++;
// leases
m_Leases = m_Buffer + offset;
auto currentTime = i2p::util::GetMillisecondsSinceEpoch ();
for (int i = 0; i < num; i++)
{
memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
offset += 32; // gateway id
htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
offset += 4; // tunnel id
uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
ts *= 1000; // in milliseconds
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
// make sure leaseset is newer than previous, but adding some time to expiration date
ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
htobe64buf (m_Buffer + offset, ts);
offset += 8; // end date
}
// we don't sign it yet. must be signed later on
}
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len):
m_ExpirationTime (0), m_Identity (identity)
{
m_BufferLen = len;
m_Buffer = new uint8_t[m_BufferLen];
memcpy (m_Buffer, buf, len);
}
bool LocalLeaseSet::IsExpired () const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
return ts > m_ExpirationTime;
}
}
}

View File

@@ -1,119 +0,0 @@
#ifndef LEASE_SET_H__
#define LEASE_SET_H__
#include <inttypes.h>
#include <string.h>
#include <vector>
#include <set>
#include <memory>
#include "Identity.h"
namespace i2p
{
namespace tunnel
{
class InboundTunnel;
}
namespace data
{
const int LEASE_ENDDATE_THRESHOLD = 51000; // in milliseconds
struct Lease
{
IdentHash tunnelGateway;
uint32_t tunnelID;
uint64_t endDate; // 0 means invalid
bool isUpdated; // trasient
};
struct LeaseCmp
{
bool operator() (std::shared_ptr<const Lease> l1, std::shared_ptr<const Lease> l2) const
{
if (l1->tunnelID != l2->tunnelID)
return l1->tunnelID < l2->tunnelID;
else
return l1->tunnelGateway < l2->tunnelGateway;
};
};
const size_t MAX_LS_BUFFER_SIZE = 3072;
const size_t LEASE_SIZE = 44; // 32 + 4 + 8
const uint8_t MAX_NUM_LEASES = 16;
class LeaseSet: public RoutingDestination
{
public:
LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true);
~LeaseSet () { delete[] m_Buffer; };
void Update (const uint8_t * buf, size_t len);
bool IsNewer (const uint8_t * buf, size_t len) const;
void PopulateLeases (); // from buffer
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
const uint8_t * GetBuffer () const { return m_Buffer; };
size_t GetBufferLen () const { return m_BufferLen; };
bool IsValid () const { return m_IsValid; };
const std::vector<std::shared_ptr<const Lease> > GetNonExpiredLeases (bool withThreshold = true) const;
bool HasExpiredLeases () const;
bool IsExpired () const;
bool IsEmpty () const { return m_Leases.empty (); };
uint64_t GetExpirationTime () const { return m_ExpirationTime; };
bool operator== (const LeaseSet& other) const
{ return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); };
// implements RoutingDestination
const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); };
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; };
bool IsDestination () const { return true; };
private:
void ReadFromBuffer (bool readIdentity = true);
uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // min expiration time
private:
bool m_IsValid, m_StoreLeases; // we don't need to store leases for floodfill
std::set<std::shared_ptr<Lease>, LeaseCmp> m_Leases;
uint64_t m_ExpirationTime; // in milliseconds
std::shared_ptr<const IdentityEx> m_Identity;
uint8_t m_EncryptionKey[256];
uint8_t * m_Buffer;
size_t m_BufferLen;
};
class LocalLeaseSet
{
public:
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len);
~LocalLeaseSet () { delete[] m_Buffer; };
const uint8_t * GetBuffer () const { return m_Buffer; };
uint8_t * GetSignature () { return m_Buffer + m_BufferLen - GetSignatureLen (); };
size_t GetBufferLen () const { return m_BufferLen; };
size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); };
uint8_t * GetLeases () { return m_Leases; };
const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); };
bool IsExpired () const;
uint64_t GetExpirationTime () const { return m_ExpirationTime; };
void SetExpirationTime (uint64_t expirationTime) { m_ExpirationTime = expirationTime; };
bool operator== (const LeaseSet& other) const
{ return m_BufferLen == other.GetBufferLen () && !memcmp (other.GetBuffer (), other.GetBuffer (), m_BufferLen); };
private:
uint64_t m_ExpirationTime; // in milliseconds
std::shared_ptr<const IdentityEx> m_Identity;
uint8_t * m_Buffer, * m_Leases;
size_t m_BufferLen;
};
}
}
#endif

171
Log.cpp
View File

@@ -1,171 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "Log.h"
namespace i2p {
namespace log {
Log logger;
/**
* @brief Maps our loglevel to their symbolic name
*/
static const char * g_LogLevelStr[eNumLogLevels] =
{
"error", // eLogError
"warn", // eLogWarn
"info", // eLogInfo
"debug" // eLogDebug
};
#ifndef _WIN32
/**
* @brief Maps our log levels to syslog one
* @return syslog priority LOG_*, as defined in syslog.h
*/
static inline int GetSyslogPrio (enum LogLevel l) {
int priority = LOG_DEBUG;
switch (l) {
case eLogError : priority = LOG_ERR; break;
case eLogWarning : priority = LOG_WARNING; break;
case eLogInfo : priority = LOG_INFO; break;
case eLogDebug : priority = LOG_DEBUG; break;
default : priority = LOG_DEBUG; break;
}
return priority;
}
#endif
Log::Log():
m_Destination(eLogStdout), m_MinLevel(eLogInfo),
m_LogStream (nullptr), m_Logfile(""), m_IsReady(false)
{
}
Log::~Log ()
{
switch (m_Destination) {
#ifndef _WIN32
case eLogSyslog :
closelog();
break;
#endif
case eLogFile:
case eLogStream:
if (m_LogStream) m_LogStream->flush();
break;
default:
/* do nothing */
break;
}
Process();
}
void Log::SetLogLevel (const std::string& level) {
if (level == "error") { m_MinLevel = eLogError; }
else if (level == "warn") { m_MinLevel = eLogWarning; }
else if (level == "info") { m_MinLevel = eLogInfo; }
else if (level == "debug") { m_MinLevel = eLogDebug; }
else {
LogPrint(eLogError, "Log: unknown loglevel: ", level);
return;
}
LogPrint(eLogInfo, "Log: min messages level set to ", level);
}
const char * Log::TimeAsString(std::time_t t) {
if (t != m_LastTimestamp) {
strftime(m_LastDateTime, sizeof(m_LastDateTime), "%H:%M:%S", localtime(&t));
m_LastTimestamp = t;
}
return m_LastDateTime;
}
/**
* @note This function better to be run in separate thread due to disk i/o.
* Unfortunately, with current startup process with late fork() this
* will give us nothing but pain. Maybe later. See in NetDb as example.
*/
void Log::Process() {
std::unique_lock<std::mutex> l(m_OutputLock);
std::hash<std::thread::id> hasher;
unsigned short short_tid;
while (1) {
auto msg = m_Queue.GetNextWithTimeout (1);
if (!msg)
break;
short_tid = (short) (hasher(msg->tid) % 1000);
switch (m_Destination) {
#ifndef _WIN32
case eLogSyslog:
syslog(GetSyslogPrio(msg->level), "[%03u] %s", short_tid, msg->text.c_str());
break;
#endif
case eLogFile:
case eLogStream:
if (m_LogStream)
*m_LogStream << TimeAsString(msg->timestamp)
<< "@" << short_tid
<< "/" << g_LogLevelStr[msg->level]
<< " - " << msg->text << std::endl;
break;
case eLogStdout:
default:
std::cout << TimeAsString(msg->timestamp)
<< "@" << short_tid
<< "/" << g_LogLevelStr[msg->level]
<< " - " << msg->text << std::endl;
break;
} // switch
} // while
}
void Log::Append(std::shared_ptr<i2p::log::LogMsg> & msg) {
m_Queue.Put(msg);
if (!m_IsReady)
return;
Process();
}
void Log::SendTo (const std::string& path)
{
if (m_LogStream) m_LogStream = nullptr; // close previous
auto flags = std::ofstream::out | std::ofstream::app;
auto os = std::make_shared<std::ofstream> (path, flags);
if (os->is_open ())
{
m_Logfile = path;
m_Destination = eLogFile;
m_LogStream = os;
return;
}
LogPrint(eLogError, "Log: can't open file ", path);
}
void Log::SendTo (std::shared_ptr<std::ostream> os) {
m_Destination = eLogStream;
m_LogStream = os;
}
#ifndef _WIN32
void Log::SendTo(const char *name, int facility) {
m_Destination = eLogSyslog;
m_LogStream = nullptr;
openlog(name, LOG_CONS | LOG_PID, facility);
}
#endif
void Log::Reopen() {
if (m_Destination == eLogFile)
SendTo(m_Logfile);
}
Log & Logger() {
return logger;
}
} // log
} // i2p

187
Log.h
View File

@@ -1,187 +0,0 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef LOG_H__
#define LOG_H__
#include <ctime>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <chrono>
#include <memory>
#include "Queue.h"
#ifndef _WIN32
#include <syslog.h>
#endif
enum LogLevel
{
eLogError = 0,
eLogWarning,
eLogInfo,
eLogDebug,
eNumLogLevels
};
enum LogType {
eLogStdout = 0,
eLogStream,
eLogFile,
#ifndef _WIN32
eLogSyslog,
#endif
};
namespace i2p {
namespace log {
struct LogMsg; /* forward declaration */
class Log
{
private:
enum LogType m_Destination;
enum LogLevel m_MinLevel;
std::shared_ptr<std::ostream> m_LogStream;
std::string m_Logfile;
std::time_t m_LastTimestamp;
char m_LastDateTime[64];
i2p::util::Queue<std::shared_ptr<LogMsg> > m_Queue;
volatile bool m_IsReady;
mutable std::mutex m_OutputLock;
private:
/** prevent making copies */
Log (const Log &);
const Log& operator=(const Log&);
/**
* @brief process stored messages in queue
*/
void Process ();
/**
* @brief Makes formatted string from unix timestamp
* @param ts Second since epoch
*
* This function internally caches the result for last provided value
*/
const char * TimeAsString(std::time_t ts);
public:
Log ();
~Log ();
LogType GetLogType () { return m_Destination; };
LogLevel GetLogLevel () { return m_MinLevel; };
/**
* @brief Sets minimal allowed level for log messages
* @param level String with wanted minimal msg level
*/
void SetLogLevel (const std::string& level);
/**
* @brief Sets log destination to logfile
* @param path Path to logfile
*/
void SendTo (const std::string &path);
/**
* @brief Sets log destination to given output stream
* @param os Output stream
*/
void SendTo (std::shared_ptr<std::ostream> os);
#ifndef _WIN32
/**
* @brief Sets log destination to syslog
* @param name Wanted program name
* @param facility Wanted log category
*/
void SendTo (const char *name, int facility);
#endif
/**
* @brief Format log message and write to output stream/syslog
* @param msg Pointer to processed message
*/
void Append(std::shared_ptr<i2p::log::LogMsg> &);
/** @brief Allow log output */
void Ready() { m_IsReady = true; }
/** @brief Flushes the output log stream */
void Flush();
/** @brief Reopen log file */
void Reopen();
};
/**
* @struct LogMsg
* @brief Log message container
*
* We creating it somewhere with LogPrint(),
* then put in MsgQueue for later processing.
*/
struct LogMsg {
std::time_t timestamp;
std::string text; /**< message text as single string */
LogLevel level; /**< message level */
std::thread::id tid; /**< id of thread that generated message */
LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {};
};
Log & Logger();
} // log
} // i2p
/** internal usage only -- folding args array to single string */
template<typename TValue>
void LogPrint (std::stringstream& s, TValue arg)
{
s << arg;
}
/** internal usage only -- folding args array to single string */
template<typename TValue, typename... TArgs>
void LogPrint (std::stringstream& s, TValue arg, TArgs... args)
{
LogPrint (s, arg);
LogPrint (s, args...);
}
/**
* @brief Create log message and send it to queue
* @param level Message level (eLogError, eLogInfo, ...)
* @param args Array of message parts
*/
template<typename... TArgs>
void LogPrint (LogLevel level, TArgs... args)
{
i2p::log::Log &log = i2p::log::Logger();
if (level > log.GetLogLevel ())
return;
// fold message to single string
std::stringstream ss("");
LogPrint (ss, args ...);
auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), ss.str());
msg->tid = std::this_thread::get_id();
log.Append(msg);
}
#endif // LOG_H__

100
Makefile
View File

@@ -1,100 +0,0 @@
UNAME := $(shell uname -s)
SHLIB := libi2pd.so
ARLIB := libi2pd.a
SHLIB_CLIENT := libi2pdclient.so
ARLIB_CLIENT := libi2pdclient.a
I2PD := i2pd
GREP := fgrep
DEPS := obj/make.dep
include filelist.mk
USE_AESNI := yes
USE_STATIC := no
ifeq ($(UNAME),Darwin)
DAEMON_SRC += DaemonLinux.cpp
ifeq ($(HOMEBREW),1)
include Makefile.homebrew
else
include Makefile.osx
endif
else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1)
DAEMON_SRC += DaemonLinux.cpp
include Makefile.bsd
else ifeq ($(UNAME),Linux)
DAEMON_SRC += DaemonLinux.cpp
include Makefile.linux
else # win32 mingw
DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp
include Makefile.mingw
endif
all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD)
mk_obj_dir:
@mkdir -p obj
@mkdir -p obj/Win32
api: mk_obj_dir $(SHLIB) $(ARLIB)
api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT)
## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time
## **without** overwriting the CXXFLAGS which we need in order to build.
## For example, when adding 'hardening flags' to the build
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FLAGS to work at build-time.
deps: mk_obj_dir
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS)
@sed -i -e '/\.o:/ s/^/obj\//' $(DEPS)
obj/%.o: %.cpp
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $<
# '-' is 'ignore if missing' on first run
-include $(DEPS)
DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC))
$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT)
$(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS)
$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC))
ifneq ($(USE_STATIC),yes)
$(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^
endif
$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC))
$(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^
$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC))
ar -r $@ $^
$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC))
ar -r $@ $^
clean:
rm -rf obj
rm -rf docs/generated
$(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT)
strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB)
strip $^
LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl)
dist:
git archive --format=tar.gz -9 --worktree-attributes \
--prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz
doxygen:
doxygen -s docs/Doxyfile
.PHONY: all
.PHONY: clean
.PHONY: deps
.PHONY: doxygen
.PHONY: dist
.PHONY: api
.PHONY: api_client
.PHONY: mk_obj_dir

View File

@@ -1,12 +0,0 @@
CXX = clang++
CXXFLAGS = -O2
## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time
## **without** overwriting the CXXFLAGS which we need in order to build.
## For example, when adding 'hardening flags' to the build
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FLAGS to work at build-time.
NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
INCFLAGS = -I/usr/include/ -I/usr/local/include/
LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib
LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread

View File

@@ -1,29 +0,0 @@
# root directory holding homebrew
BREWROOT = /usr/local/
BOOSTROOT = ${BREWROOT}/opt/boost
SSLROOT = ${BREWROOT}/opt/libressl
CXX = clang++
CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX
INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include
LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),1)
LDFLAGS += -ldl
CXXFLAGS += -DUSE_UPNP
endif
# OSX Notes
# http://www.hutsby.net/2011/08/macs-with-aes-ni.html
# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2
# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic
# note from psi: 2009 macbook does not have aesni
#ifeq ($(USE_AESNI),yes)
# CXXFLAGS += -maes -DAESNI
#endif
# Disabled, since it will be the default make rule. I think its better
# to define the default rule in Makefile and not Makefile.<ostype> - torkel
#install: all
# test -d ${PREFIX} || mkdir -p ${PREFIX}/
# cp -r i2p ${PREFIX}/

View File

@@ -1,59 +0,0 @@
# set defaults instead redefine
CXXFLAGS ?= -g -Wall -Wextra -Wno-unused-parameter -pedantic
INCFLAGS ?=
## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time
## **without** overwriting the CXXFLAGS which we need in order to build.
## For example, when adding 'hardening flags' to the build
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FLAGS to work at build-time.
# detect proper flag for c++11 support by compilers
CXXVER := $(shell $(CXX) -dumpversion)
ifeq ($(shell expr match $(CXX) 'clang'),5)
NEEDED_CXXFLAGS += -std=c++11
else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10
NEEDED_CXXFLAGS += -std=c++11
else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7
NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6
NEEDED_CXXFLAGS += -std=c++0x
else ifeq ($(shell expr match ${CXXVER} "[5-6]\.[0-9]"),3) # gcc >= 5.0
NEEDED_CXXFLAGS += -std=c++11
else # not supported
$(error Compiler too old)
endif
NEEDED_CXXFLAGS += -fPIC
ifeq ($(USE_STATIC),yes)
LIBDIR := /usr/lib
LDLIBS = $(LIBDIR)/libboost_system.a
LDLIBS += $(LIBDIR)/libboost_date_time.a
LDLIBS += $(LIBDIR)/libboost_filesystem.a
LDLIBS += $(LIBDIR)/libboost_program_options.a
LDLIBS += $(LIBDIR)/libcrypto.a
LDLIBS += $(LIBDIR)/libssl.a
LDLIBS += $(LIBDIR)/libz.a
LDLIBS += -lpthread -static-libstdc++ -static-libgcc
USE_AESNI := no
else
LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
endif
# UPNP Support (miniupnpc 1.5 or 1.6)
ifeq ($(USE_UPNP),1)
LDFLAGS += -ldl
CXXFLAGS += -DUSE_UPNP
endif
IS_64 := $(shell $(CXX) -dumpmachine 2>&1 | $(GREP) -c "64")
ifeq ($(USE_AESNI),yes)
ifeq ($(IS_64),1)
#check if AES-NI is supported by CPU
ifneq ($(shell grep -c aes /proc/cpuinfo),0)
CPU_FLAGS = -maes -DAESNI
endif
endif
endif

View File

@@ -1,42 +0,0 @@
USE_WIN32_APP=yes
CXX = g++
WINDRES = windres
CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN
NEEDED_CXXFLAGS = -std=c++11
BOOST_SUFFIX = -mt
INCFLAGS = -I/usr/include/ -I/usr/local/include/
LDFLAGS = -Wl,-rpath,/usr/local/lib \
-L/usr/local/lib \
-L/c/dev/openssl \
-L/c/dev/boost/lib
LDLIBS = \
-Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \
-Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \
-Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \
-Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \
-Wl,-Bstatic -lssl \
-Wl,-Bstatic -lcrypto \
-Wl,-Bstatic -lz \
-Wl,-Bstatic -lwsock32 \
-Wl,-Bstatic -lws2_32 \
-Wl,-Bstatic -lgdi32 \
-Wl,-Bstatic -liphlpapi \
-static-libgcc -static-libstdc++ \
-Wl,-Bstatic -lstdc++ \
-Wl,-Bstatic -lpthread
ifeq ($(USE_WIN32_APP), yes)
CXXFLAGS += -DWIN32_APP
LDFLAGS += -mwindows -s
DAEMON_RC += Win32/Resource.rc
DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC))
endif
ifeq ($(USE_AESNI),1)
CPU_FLAGS = -maes -DAESNI
else
CPU_FLAGS = -msse
endif
obj/%.o : %.rc
$(WINDRES) -i $< -o $@

View File

@@ -1,25 +0,0 @@
CXX = clang++
CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX
#CXXFLAGS = -g -O2 -Wall -std=c++11
INCFLAGS = -I/usr/local/include -I/usr/local/ssl/include
LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/usr/local/ssl/lib
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),1)
LDFLAGS += -ldl
CXXFLAGS += -DUSE_UPNP
endif
# OSX Notes
# http://www.hutsby.net/2011/08/macs-with-aes-ni.html
# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2
# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic
ifeq ($(USE_AESNI),yes)
CXXFLAGS += -maes -DAESNI
endif
# Disabled, since it will be the default make rule. I think its better
# to define the default rule in Makefile and not Makefile.<ostype> - torkel
#install: all
# test -d ${PREFIX} || mkdir -p ${PREFIX}/
# cp -r i2p ${PREFIX}/

View File

@@ -1,966 +0,0 @@
#include <string.h>
#include <stdlib.h>
#include <zlib.h>
#include "I2PEndian.h"
#include "Base.h"
#include "Crypto.h"
#include "Log.h"
#include "Timestamp.h"
#include "I2NPProtocol.h"
#include "RouterContext.h"
#include "Transports.h"
#include "NetDb.h"
#include "NTCPSession.h"
using namespace i2p::crypto;
namespace i2p
{
namespace transport
{
NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter):
TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()),
m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false),
m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false)
{
m_Establisher = new Establisher;
}
NTCPSession::~NTCPSession ()
{
delete m_Establisher;
}
void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key)
{
uint8_t sharedKey[256];
m_DHKeysPair->Agree (pubKey, sharedKey);
uint8_t * aesKey = key;
if (sharedKey[0] & 0x80)
{
aesKey[0] = 0;
memcpy (aesKey + 1, sharedKey, 31);
}
else if (sharedKey[0])
memcpy (aesKey, sharedKey, 32);
else
{
// find first non-zero byte
uint8_t * nonZero = sharedKey + 1;
while (!*nonZero)
{
nonZero++;
if (nonZero - sharedKey > 32)
{
LogPrint (eLogWarning, "NTCP: First 32 bytes of shared key is all zeros, ignored");
return;
}
}
memcpy (aesKey, nonZero, 32);
}
}
void NTCPSession::Done ()
{
m_Server.GetService ().post (std::bind (&NTCPSession::Terminate, shared_from_this ()));
}
void NTCPSession::Terminate ()
{
if (!m_IsTerminated)
{
m_IsTerminated = true;
m_IsEstablished = false;
m_Socket.close ();
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCPSession (shared_from_this ());
m_SendQueue.clear ();
m_NextMessage = nullptr;
m_TerminationTimer.cancel ();
LogPrint (eLogDebug, "NTCP: session terminated");
}
}
void NTCPSession::Connected ()
{
m_IsEstablished = true;
delete m_Establisher;
m_Establisher = nullptr;
m_DHKeysPair = nullptr;
SendTimeSyncMessage ();
transports.PeerConnected (shared_from_this ());
}
void NTCPSession::ClientLogin ()
{
if (!m_DHKeysPair)
m_DHKeysPair = transports.GetNextDHKeysPair ();
// send Phase1
const uint8_t * x = m_DHKeysPair->GetPublicKey ();
memcpy (m_Establisher->phase1.pubKey, x, 256);
SHA256(x, 256, m_Establisher->phase1.HXxorHI);
const uint8_t * ident = m_RemoteIdentity->GetIdentHash ();
for (int i = 0; i < 32; i++)
m_Establisher->phase1.HXxorHI[i] ^= ident[i];
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
ScheduleTermination ();
}
void NTCPSession::ServerLogin ()
{
boost::system::error_code ec;
auto ep = m_Socket.remote_endpoint(ec);
if (!ec)
{
m_ConnectedFrom = ep.address ();
// receive Phase1
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
ScheduleTermination ();
}
}
void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogInfo, "NTCP: couldn't send Phase 1 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase2Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
}
void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogInfo, "NTCP: phase 1 read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
// verify ident
uint8_t digest[32];
SHA256(m_Establisher->phase1.pubKey, 256, digest);
const uint8_t * ident = i2p::context.GetIdentHash ();
for (int i = 0; i < 32; i++)
{
if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i])
{
LogPrint (eLogError, "NTCP: phase 1 error: ident mismatch");
Terminate ();
return;
}
}
SendPhase2 ();
}
}
void NTCPSession::SendPhase2 ()
{
if (!m_DHKeysPair)
m_DHKeysPair = transports.GetNextDHKeysPair ();
const uint8_t * y = m_DHKeysPair->GetPublicKey ();
memcpy (m_Establisher->phase2.pubKey, y, 256);
uint8_t xy[512];
memcpy (xy, m_Establisher->phase1.pubKey, 256);
memcpy (xy + 256, y, 256);
SHA256(xy, 512, m_Establisher->phase2.encrypted.hxy);
uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ());
memcpy (m_Establisher->phase2.encrypted.timestamp, &tsB, 4);
RAND_bytes (m_Establisher->phase2.encrypted.filler, 12);
i2p::crypto::AESKey aesKey;
CreateAESKey (m_Establisher->phase1.pubKey, aesKey);
m_Encryption.SetKey (aesKey);
m_Encryption.SetIV (y + 240);
m_Decryption.SetKey (aesKey);
m_Decryption.SetIV (m_Establisher->phase1.HXxorHI + 16);
m_Encryption.Encrypt ((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted);
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase2Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsB));
}
void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogInfo, "NTCP: Couldn't send Phase 2 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase3Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, tsB));
}
}
void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogInfo, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed");
if (ecode != boost::asio::error::operation_aborted)
{
// this RI is not valid
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true);
transports.ReuseDHKeysPair (m_DHKeysPair);
m_DHKeysPair = nullptr;
Terminate ();
}
}
else
{
i2p::crypto::AESKey aesKey;
CreateAESKey (m_Establisher->phase2.pubKey, aesKey);
m_Decryption.SetKey (aesKey);
m_Decryption.SetIV (m_Establisher->phase2.pubKey + 240);
m_Encryption.SetKey (aesKey);
m_Encryption.SetIV (m_Establisher->phase1.HXxorHI + 16);
m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted);
// verify
uint8_t xy[512];
memcpy (xy, m_DHKeysPair->GetPublicKey (), 256);
memcpy (xy + 256, m_Establisher->phase2.pubKey, 256);
uint8_t digest[32];
SHA256 (xy, 512, digest);
if (memcmp(m_Establisher->phase2.encrypted.hxy, digest, 32))
{
LogPrint (eLogError, "NTCP: Phase 2 process error: incorrect hash");
transports.ReuseDHKeysPair (m_DHKeysPair);
m_DHKeysPair = nullptr;
Terminate ();
return ;
}
SendPhase3 ();
}
}
void NTCPSession::SendPhase3 ()
{
auto keys = i2p::context.GetPrivateKeys ();
uint8_t * buf = m_ReceiveBuffer;
htobe16buf (buf, keys.GetPublic ()->GetFullLen ());
buf += 2;
buf += i2p::context.GetIdentity ()->ToBuffer (buf, NTCP_BUFFER_SIZE);
uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ());
htobuf32(buf,tsA);
buf += 4;
size_t signatureLen = keys.GetPublic ()->GetSignatureLen ();
size_t len = (buf - m_ReceiveBuffer) + signatureLen;
size_t paddingSize = len & 0x0F; // %16
if (paddingSize > 0)
{
paddingSize = 16 - paddingSize;
// fill padding with random data
RAND_bytes(buf, paddingSize);
buf += paddingSize;
len += paddingSize;
}
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB
s.Sign (keys, buf);
m_Encryption.Encrypt(m_ReceiveBuffer, len, m_ReceiveBuffer);
boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, len), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase3Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsA));
}
void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogInfo, "NTCP: Couldn't send Phase 3 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
// wait for phase4
auto signatureLen = m_RemoteIdentity->GetSignatureLen ();
size_t paddingSize = signatureLen & 0x0F; // %16
if (paddingSize > 0) signatureLen += (16 - paddingSize);
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase4Received, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, tsA));
}
}
void NTCPSession::HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
{
if (ecode)
{
LogPrint (eLogInfo, "NTCP: Phase 3 read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer);
uint8_t * buf = m_ReceiveBuffer;
uint16_t size = bufbe16toh (buf);
SetRemoteIdentity (std::make_shared<i2p::data::IdentityEx> (buf + 2, size));
if (m_Server.FindNTCPSession (m_RemoteIdentity->GetIdentHash ()))
{
LogPrint (eLogInfo, "NTCP: session already exists");
Terminate ();
}
size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen ();
size_t paddingLen = expectedSize & 0x0F;
if (paddingLen) paddingLen = (16 - paddingLen);
if (expectedSize > NTCP_DEFAULT_PHASE3_SIZE)
{
// we need more bytes for Phase3
expectedSize += paddingLen;
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize - NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, tsB, paddingLen));
}
else
HandlePhase3 (tsB, paddingLen);
}
}
void NTCPSession::HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen)
{
if (ecode)
{
LogPrint (eLogInfo, "NTCP: Phase 3 extra read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Decryption.Decrypt (m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, bytes_transferred, m_ReceiveBuffer+ NTCP_DEFAULT_PHASE3_SIZE);
HandlePhase3 (tsB, paddingLen);
}
}
void NTCPSession::HandlePhase3 (uint32_t tsB, size_t paddingLen)
{
uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity->GetFullLen () + 2 /*size*/;
uint32_t tsA = buf32toh(buf);
buf += 4;
buf += paddingLen;
// check timestamp
auto ts = i2p::util::GetSecondsSinceEpoch ();
uint32_t tsA1 = be32toh (tsA);
if (tsA1 < ts - NTCP_CLOCK_SKEW || tsA1 > ts + NTCP_CLOCK_SKEW)
{
LogPrint (eLogError, "NTCP: Phase3 time difference ", ts - tsA1, " exceeds clock skew");
Terminate ();
return;
}
// check signature
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (tsB); // tsB
if (!s.Verify (m_RemoteIdentity, buf))
{
LogPrint (eLogError, "NTCP: signature verification failed");
Terminate ();
return;
}
SendPhase4 (tsA, tsB);
}
void NTCPSession::SendPhase4 (uint32_t tsA, uint32_t tsB)
{
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (tsB); // tsB
auto keys = i2p::context.GetPrivateKeys ();
auto signatureLen = keys.GetPublic ()->GetSignatureLen ();
s.Sign (keys, m_ReceiveBuffer);
size_t paddingSize = signatureLen & 0x0F; // %16
if (paddingSize > 0) signatureLen += (16 - paddingSize);
m_Encryption.Encrypt (m_ReceiveBuffer, signatureLen, m_ReceiveBuffer);
boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandlePhase4Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogWarning, "NTCP: Couldn't send Phase 4 message: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
LogPrint (eLogInfo, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected");
m_Server.AddNTCPSession (shared_from_this ());
Connected ();
m_ReceiveBufferOffset = 0;
m_NextMessage = nullptr;
Receive ();
}
}
void NTCPSession::HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
{
if (ecode)
{
LogPrint (eLogError, "NTCP: Phase 4 read error: ", ecode.message (), ". Check your clock");
if (ecode != boost::asio::error::operation_aborted)
{
// this router doesn't like us
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true);
Terminate ();
}
}
else
{
m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer);
// check timestamp
uint32_t tsB = bufbe32toh (m_Establisher->phase2.encrypted.timestamp);
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (tsB < ts - NTCP_CLOCK_SKEW || tsB > ts + NTCP_CLOCK_SKEW)
{
LogPrint (eLogError, "NTCP: Phase4 time difference ", ts - tsB, " exceeds clock skew");
Terminate ();
return;
}
// verify signature
SignedData s;
s.Insert (m_Establisher->phase1.pubKey, 256); // x
s.Insert (m_Establisher->phase2.pubKey, 256); // y
s.Insert (i2p::context.GetIdentHash (), 32); // ident
s.Insert (tsA); // tsA
s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB
if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer))
{
LogPrint (eLogError, "NTCP: Phase 4 process error: signature verification failed");
Terminate ();
return;
}
LogPrint (eLogDebug, "NTCP: session to ", m_Socket.remote_endpoint (), " connected");
Connected ();
m_ReceiveBufferOffset = 0;
m_NextMessage = nullptr;
Receive ();
}
}
void NTCPSession::Receive ()
{
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, NTCP_BUFFER_SIZE - m_ReceiveBufferOffset),
std::bind(&NTCPSession::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode) {
if (ecode != boost::asio::error::operation_aborted)
LogPrint (eLogDebug, "NTCP: Read error: ", ecode.message ());
if (!m_NumReceivedBytes)
m_Server.Ban (m_ConnectedFrom);
//if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_NumReceivedBytes += bytes_transferred;
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred);
m_ReceiveBufferOffset += bytes_transferred;
if (m_ReceiveBufferOffset >= 16)
{
int numReloads = 0;
do
{
uint8_t * nextBlock = m_ReceiveBuffer;
while (m_ReceiveBufferOffset >= 16)
{
if (!DecryptNextBlock (nextBlock)) // 16 bytes
{
Terminate ();
return;
}
nextBlock += 16;
m_ReceiveBufferOffset -= 16;
}
if (m_ReceiveBufferOffset > 0)
memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset);
// try to read more
if (numReloads < 5)
{
boost::system::error_code ec;
size_t moreBytes = m_Socket.available(ec);
if (moreBytes)
{
if (moreBytes > NTCP_BUFFER_SIZE - m_ReceiveBufferOffset)
moreBytes = NTCP_BUFFER_SIZE - m_ReceiveBufferOffset;
moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes));
if (ec)
{
LogPrint (eLogInfo, "NTCP: Read more bytes error: ", ec.message ());
Terminate ();
return;
}
m_NumReceivedBytes += moreBytes;
m_ReceiveBufferOffset += moreBytes;
numReloads++;
}
}
}
while (m_ReceiveBufferOffset >= 16);
m_Handler.Flush ();
}
ScheduleTermination (); // reset termination timer
Receive ();
}
}
bool NTCPSession::DecryptNextBlock (const uint8_t * encrypted) // 16 bytes
{
if (!m_NextMessage) // new message, header expected
{
// decrypt header and extract length
uint8_t buf[16];
m_Decryption.Decrypt (encrypted, buf);
uint16_t dataSize = bufbe16toh (buf);
if (dataSize)
{
// new message
if (dataSize + 16U > NTCP_MAX_MESSAGE_SIZE - 2) // + 6 + padding
{
LogPrint (eLogError, "NTCP: data size ", dataSize, " exceeds max size");
return false;
}
auto msg = (dataSize + 16U) <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage ();
m_NextMessage = msg;
memcpy (m_NextMessage->buf, buf, 16);
m_NextMessageOffset = 16;
m_NextMessage->offset = 2; // size field
m_NextMessage->len = dataSize + 2;
}
else
{
// timestamp
LogPrint (eLogDebug, "NTCP: Timestamp");
return true;
}
}
else // message continues
{
m_Decryption.Decrypt (encrypted, m_NextMessage->buf + m_NextMessageOffset);
m_NextMessageOffset += 16;
}
if (m_NextMessageOffset >= m_NextMessage->len + 4) // +checksum
{
// we have a complete I2NP message
uint8_t checksum[4];
htobe32buf (checksum, adler32 (adler32 (0, Z_NULL, 0), m_NextMessage->buf, m_NextMessageOffset - 4));
if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4))
{
if (!m_NextMessage->IsExpired ())
m_Handler.PutNextMessage (m_NextMessage);
else
LogPrint (eLogInfo, "NTCP: message expired");
}
else
LogPrint (eLogWarning, "NTCP: Incorrect adler checksum of message, dropped");
m_NextMessage = nullptr;
}
return true;
}
void NTCPSession::Send (std::shared_ptr<i2p::I2NPMessage> msg)
{
m_IsSending = true;
boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (),
std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector<std::shared_ptr<I2NPMessage> >{ msg }));
}
boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg)
{
uint8_t * sendBuffer;
int len;
if (msg)
{
// regular I2NP
if (msg->offset < 2)
LogPrint (eLogError, "NTCP: Malformed I2NP message"); // TODO:
sendBuffer = msg->GetBuffer () - 2;
len = msg->GetLength ();
htobe16buf (sendBuffer, len);
}
else
{
// prepare timestamp
sendBuffer = m_TimeSyncBuffer;
len = 4;
htobuf16(sendBuffer, 0);
htobe32buf (sendBuffer + 2, time (0));
}
int rem = (len + 6) & 0x0F; // %16
int padding = 0;
if (rem > 0) {
padding = 16 - rem;
// fill with random padding
RAND_bytes(sendBuffer + len + 2, padding);
}
htobe32buf (sendBuffer + len + 2 + padding, adler32 (adler32 (0, Z_NULL, 0), sendBuffer, len + 2+ padding));
int l = len + padding + 6;
m_Encryption.Encrypt(sendBuffer, l, sendBuffer);
return boost::asio::buffer ((const uint8_t *)sendBuffer, l);
}
void NTCPSession::Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
m_IsSending = true;
std::vector<boost::asio::const_buffer> bufs;
for (auto it: msgs)
bufs.push_back (CreateMsgBuffer (it));
boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (),
std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs));
}
void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
(void) msgs;
m_IsSending = false;
if (ecode)
{
LogPrint (eLogWarning, "NTCP: Couldn't send msgs: ", ecode.message ());
// we shouldn't call Terminate () here, because HandleReceive takes care
// TODO: 'delete this' statement in Terminate () must be eliminated later
// Terminate ();
}
else
{
m_NumSentBytes += bytes_transferred;
i2p::transport::transports.UpdateSentBytes (bytes_transferred);
if (!m_SendQueue.empty())
{
Send (m_SendQueue);
m_SendQueue.clear ();
}
else
ScheduleTermination (); // reset termination timer
}
}
void NTCPSession::SendTimeSyncMessage ()
{
Send (nullptr);
}
void NTCPSession::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs));
}
void NTCPSession::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
if (m_IsTerminated) return;
if (m_IsSending)
{
for (auto it: msgs)
m_SendQueue.push_back (it);
}
else
Send (msgs);
}
void NTCPSession::ScheduleTermination ()
{
m_TerminationTimer.cancel ();
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_TIMEOUT));
m_TerminationTimer.async_wait (std::bind (&NTCPSession::HandleTerminationTimer,
shared_from_this (), std::placeholders::_1));
}
void NTCPSession::HandleTerminationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
LogPrint (eLogDebug, "NTCP: No activity for ", NTCP_TERMINATION_TIMEOUT, " seconds");
//Terminate ();
m_Socket.close ();// invoke Terminate () from HandleReceive
}
}
//-----------------------------------------
NTCPServer::NTCPServer ():
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr)
{
}
NTCPServer::~NTCPServer ()
{
Stop ();
}
void NTCPServer::Start ()
{
if (!m_IsRunning)
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&NTCPServer::Run, this));
// create acceptors
auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (auto address: addresses)
{
if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && address->host.is_v4 ())
{
m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port));
LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port);
auto conn = std::make_shared<NTCPSession>(*this);
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
conn, std::placeholders::_1));
if (context.SupportsV6 ())
{
m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service);
m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6());
m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true));
m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port));
m_NTCPV6Acceptor->listen ();
LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port);
auto conn = std::make_shared<NTCPSession> (*this);
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6,
this, conn, std::placeholders::_1));
}
}
}
}
}
void NTCPServer::Stop ()
{
m_NTCPSessions.clear ();
if (m_IsRunning)
{
m_IsRunning = false;
delete m_NTCPAcceptor;
m_NTCPAcceptor = nullptr;
delete m_NTCPV6Acceptor;
m_NTCPV6Acceptor = nullptr;
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
}
void NTCPServer::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "NTCP: runtime exception: ", ex.what ());
}
}
}
bool NTCPServer::AddNTCPSession (std::shared_ptr<NTCPSession> session)
{
if (!session || !session->GetRemoteIdentity ()) return false;
auto& ident = session->GetRemoteIdentity ()->GetIdentHash ();
auto it = m_NTCPSessions.find (ident);
if (it != m_NTCPSessions.end ())
{
LogPrint (eLogWarning, "NTCP: session to ", ident.ToBase64 (), " already exists");
return false;
}
m_NTCPSessions.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<NTCPSession> >(ident, session));
return true;
}
void NTCPServer::RemoveNTCPSession (std::shared_ptr<NTCPSession> session)
{
if (session && session->GetRemoteIdentity ())
m_NTCPSessions.erase (session->GetRemoteIdentity ()->GetIdentHash ());
}
std::shared_ptr<NTCPSession> NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident)
{
auto it = m_NTCPSessions.find (ident);
if (it != m_NTCPSessions.end ())
return it->second;
return nullptr;
}
void NTCPServer::HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error)
{
if (!error)
{
boost::system::error_code ec;
auto ep = conn->GetSocket ().remote_endpoint(ec);
if (!ec)
{
LogPrint (eLogDebug, "NTCP: Connected from ", ep);
auto it = m_BanList.find (ep.address ());
if (it != m_BanList.end ())
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second)
{
LogPrint (eLogWarning, "NTCP: ", ep.address (), " is banned for ", it->second - ts, " more seconds");
conn = nullptr;
}
else
m_BanList.erase (it);
}
if (conn)
conn->ServerLogin ();
}
else
LogPrint (eLogError, "NTCP: Connected from error ", ec.message ());
}
if (error != boost::asio::error::operation_aborted)
{
conn = std::make_shared<NTCPSession> (*this);
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
conn, std::placeholders::_1));
}
}
void NTCPServer::HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error)
{
if (!error)
{
boost::system::error_code ec;
auto ep = conn->GetSocket ().remote_endpoint(ec);
if (!ec)
{
LogPrint (eLogDebug, "NTCP: Connected from ", ep);
auto it = m_BanList.find (ep.address ());
if (it != m_BanList.end ())
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < it->second)
{
LogPrint (eLogWarning, "NTCP: ", ep.address (), " is banned for ", it->second - ts, " more seconds");
conn = nullptr;
}
else
m_BanList.erase (it);
}
if (conn)
conn->ServerLogin ();
}
else
LogPrint (eLogError, "NTCP: Connected from error ", ec.message ());
}
if (error != boost::asio::error::operation_aborted)
{
conn = std::make_shared<NTCPSession> (*this);
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this,
conn, std::placeholders::_1));
}
}
void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn)
{
LogPrint (eLogDebug, "NTCP: Connecting to ", address ,":", port);
m_Service.post([=]()
{
if (this->AddNTCPSession (conn))
conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port),
std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn));
});
}
void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn)
{
if (ecode)
{
LogPrint (eLogError, "NTCP: Connect error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true);
conn->Terminate ();
}
else
{
LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ());
if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6
context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ());
conn->ClientLogin ();
}
}
void NTCPServer::Ban (const boost::asio::ip::address& addr)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
m_BanList[addr] = ts + NTCP_BAN_EXPIRATION_TIMEOUT;
LogPrint (eLogWarning, "NTCP: ", addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds");
}
}
}

View File

@@ -1,177 +0,0 @@
#ifndef NTCP_SESSION_H__
#define NTCP_SESSION_H__
#include <inttypes.h>
#include <map>
#include <memory>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>
#include "Crypto.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "TransportSession.h"
namespace i2p
{
namespace transport
{
struct NTCPPhase1
{
uint8_t pubKey[256];
uint8_t HXxorHI[32];
};
struct NTCPPhase2
{
uint8_t pubKey[256];
struct
{
uint8_t hxy[32];
uint8_t timestamp[4];
uint8_t filler[12];
} encrypted;
};
const size_t NTCP_MAX_MESSAGE_SIZE = 16384;
const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028)
const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes
const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448
const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second
const int NTCP_CLOCK_SKEW = 60; // in seconds
class NTCPServer;
class NTCPSession: public TransportSession, public std::enable_shared_from_this<NTCPSession>
{
public:
NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr);
~NTCPSession ();
void Terminate ();
void Done ();
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
bool IsEstablished () const { return m_IsEstablished; };
void ClientLogin ();
void ServerLogin ();
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
private:
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void Connected ();
void SendTimeSyncMessage ();
void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; }
void CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key);
// client
void SendPhase3 ();
void HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
void HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
//server
void SendPhase2 ();
void SendPhase4 (uint32_t tsA, uint32_t tsB);
void HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
void HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
void HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen);
void HandlePhase3 (uint32_t tsB, size_t paddingLen);
void HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
// common
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
bool DecryptNextBlock (const uint8_t * encrypted);
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg);
void Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs);
// timer
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
private:
NTCPServer& m_Server;
boost::asio::ip::tcp::socket m_Socket;
boost::asio::deadline_timer m_TerminationTimer;
bool m_IsEstablished, m_IsTerminated;
i2p::crypto::CBCDecryption m_Decryption;
i2p::crypto::CBCEncryption m_Encryption;
struct Establisher
{
NTCPPhase1 phase1;
NTCPPhase2 phase2;
} * m_Establisher;
i2p::crypto::AESAlignedBuffer<NTCP_BUFFER_SIZE + 16> m_ReceiveBuffer;
i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer;
int m_ReceiveBufferOffset;
std::shared_ptr<I2NPMessage> m_NextMessage;
size_t m_NextMessageOffset;
i2p::I2NPMessagesHandler m_Handler;
bool m_IsSending;
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
boost::asio::ip::address m_ConnectedFrom; // for ban
};
// TODO: move to NTCP.h/.cpp
class NTCPServer
{
public:
NTCPServer ();
~NTCPServer ();
void Start ();
void Stop ();
bool AddNTCPSession (std::shared_ptr<NTCPSession> session);
void RemoveNTCPSession (std::shared_ptr<NTCPSession> session);
std::shared_ptr<NTCPSession> FindNTCPSession (const i2p::data::IdentHash& ident);
void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn);
boost::asio::io_service& GetService () { return m_Service; };
void Ban (const boost::asio::ip::address& addr);
private:
void Run ();
void HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
void HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor;
std::map<i2p::data::IdentHash, std::shared_ptr<NTCPSession> > m_NTCPSessions; // access from m_Thread only
std::map<boost::asio::ip::address, uint32_t> m_BanList; // IP -> ban expiration time in seconds
public:
// for HTTP/I2PControl
const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; };
};
}
}
#endif

1008
NetDb.cpp

File diff suppressed because it is too large Load Diff

120
NetDb.h
View File

@@ -1,120 +0,0 @@
#ifndef NETDB_H__
#define NETDB_H__
#include <inttypes.h>
#include <set>
#include <map>
#include <list>
#include <string>
#include <thread>
#include <mutex>
#include "Base.h"
#include "FS.h"
#include "Queue.h"
#include "I2NPProtocol.h"
#include "RouterInfo.h"
#include "LeaseSet.h"
#include "Tunnel.h"
#include "TunnelPool.h"
#include "Reseed.h"
#include "NetDbRequests.h"
#include "Family.h"
namespace i2p
{
namespace data
{
const int NETDB_MIN_ROUTERS = 90;
const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds
const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60;
const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours
const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours
class NetDb
{
public:
NetDb ();
~NetDb ();
void Start ();
void Stop ();
bool AddRouterInfo (const uint8_t * buf, int len);
bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len);
bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
std::shared_ptr<RouterInfo> FindRouter (const IdentHash& ident) const;
std::shared_ptr<LeaseSet> FindLeaseSet (const IdentHash& destination) const;
std::shared_ptr<RouterProfile> FindRouterProfile (const IdentHash& ident) const;
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr);
void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter () const;
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
void SetUnreachable (const IdentHash& ident, bool unreachable);
void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
void Reseed ();
Families& GetFamilies () { return m_Families; };
// for web interface
int GetNumRouters () const { return m_RouterInfos.size (); };
int GetNumFloodfills () const { return m_Floodfills.size (); };
int GetNumLeaseSets () const { return m_LeaseSets.size (); };
private:
void Load ();
bool LoadRouterInfo (const std::string & path);
void SaveUpdated ();
void Run (); // exploratory thread
void Explore (int numDestinations);
void Publish ();
void ManageLeaseSets ();
void ManageRequests ();
void ManageLookupResponses ();
template<typename Filter>
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
private:
std::map<IdentHash, std::shared_ptr<LeaseSet> > m_LeaseSets;
mutable std::mutex m_RouterInfosMutex;
std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos;
mutable std::mutex m_FloodfillsMutex;
std::list<std::shared_ptr<RouterInfo> > m_Floodfills;
bool m_IsRunning;
uint64_t m_LastLoad;
std::thread * m_Thread;
i2p::util::Queue<std::shared_ptr<const I2NPMessage> > m_Queue; // of I2NPDatabaseStoreMsg
GzipInflator m_Inflator;
Reseeder * m_Reseeder;
Families m_Families;
i2p::fs::HashedStorage m_Storage;
friend class NetDbRequests;
NetDbRequests m_Requests;
std::map<IdentHash, std::pair<std::vector<IdentHash>, uint64_t> > m_LookupResponses; // ident->(closest FFs, timestamp)
};
extern NetDb netdb;
}
}
#endif

View File

@@ -1,149 +0,0 @@
#include "Log.h"
#include "I2NPProtocol.h"
#include "Transports.h"
#include "NetDb.h"
#include "NetDbRequests.h"
namespace i2p
{
namespace data
{
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
{
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory,
&m_ExcludedPeers);
m_ExcludedPeers.insert (router->GetIdentHash ());
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
return msg;
}
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (const IdentHash& floodfill)
{
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
m_ExcludedPeers.insert (floodfill);
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
return msg;
}
void RequestedDestination::ClearExcludedPeers ()
{
m_ExcludedPeers.clear ();
}
void RequestedDestination::Success (std::shared_ptr<RouterInfo> r)
{
if (m_RequestComplete)
{
m_RequestComplete (r);
m_RequestComplete = nullptr;
}
}
void RequestedDestination::Fail ()
{
if (m_RequestComplete)
{
m_RequestComplete (nullptr);
m_RequestComplete = nullptr;
}
}
void NetDbRequests::Start ()
{
}
void NetDbRequests::Stop ()
{
m_RequestedDestinations.clear ();
}
std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete)
{
// request RouterInfo directly
auto dest = std::make_shared<RequestedDestination> (destination, isExploratory);
dest->SetRequestComplete (requestComplete);
{
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
if (!m_RequestedDestinations.insert (std::make_pair (destination,
std::shared_ptr<RequestedDestination> (dest))).second) // not inserted
return nullptr;
}
return dest;
}
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
{
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
{
if (r)
it->second->Success (r);
else
it->second->Fail ();
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
m_RequestedDestinations.erase (it);
}
}
std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const
{
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
return it->second;
return nullptr;
}
void NetDbRequests::ManageRequests ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
{
auto& dest = it->second;
bool done = false;
if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute
{
if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds
{
auto count = dest->GetExcludedPeers ().size ();
if (!dest->IsExploratory () && count < 7)
{
auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
auto outbound = pool->GetNextOutboundTunnel ();
auto inbound = pool->GetNextInboundTunnel ();
auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
if (nextFloodfill && outbound && inbound)
outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0,
dest->CreateRequestMessage (nextFloodfill, inbound));
else
{
done = true;
if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels");
if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels");
if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills");
}
}
else
{
if (!dest->IsExploratory ())
LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts");
done = true;
}
}
}
else // delete obsolete request
done = true;
if (done)
it = m_RequestedDestinations.erase (it);
else
it++;
}
}
}
}

View File

@@ -1,69 +0,0 @@
#ifndef NETDB_REQUESTS_H__
#define NETDB_REQUESTS_H__
#include <memory>
#include <set>
#include <map>
#include "Identity.h"
#include "RouterInfo.h"
namespace i2p
{
namespace data
{
class RequestedDestination
{
public:
typedef std::function<void (std::shared_ptr<RouterInfo>)> RequestComplete;
RequestedDestination (const IdentHash& destination, bool isExploratory = false):
m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {};
~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); };
const IdentHash& GetDestination () const { return m_Destination; };
int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
void ClearExcludedPeers ();
bool IsExploratory () const { return m_IsExploratory; };
bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
uint64_t GetCreationTime () const { return m_CreationTime; };
std::shared_ptr<I2NPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel);
std::shared_ptr<I2NPMessage> CreateRequestMessage (const IdentHash& floodfill);
void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; };
bool IsRequestComplete () const { return m_RequestComplete != nullptr; };
void Success (std::shared_ptr<RouterInfo> r);
void Fail ();
private:
IdentHash m_Destination;
bool m_IsExploratory;
std::set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime;
RequestComplete m_RequestComplete;
};
class NetDbRequests
{
public:
void Start ();
void Stop ();
std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr);
void RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r);
std::shared_ptr<RequestedDestination> FindRequest (const IdentHash& ident) const;
void ManageRequests ();
private:
std::mutex m_RequestedDestinationsMutex;
std::map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
};
}
}
#endif

View File

@@ -1,182 +0,0 @@
#include <sys/stat.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Profiling.h"
namespace i2p
{
namespace data
{
i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
RouterProfile::RouterProfile (const IdentHash& identHash):
m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
m_NumTimesTaken (0), m_NumTimesRejected (0)
{
}
boost::posix_time::ptime RouterProfile::GetTime () const
{
return boost::posix_time::second_clock::local_time();
}
void RouterProfile::UpdateTime ()
{
m_LastUpdateTime = GetTime ();
}
void RouterProfile::Save ()
{
// fill sections
boost::property_tree::ptree participation;
participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed);
participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined);
participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied);
boost::property_tree::ptree usage;
usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken);
usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected);
// fill property tree
boost::property_tree::ptree pt;
pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
pt.put_child (PEER_PROFILE_SECTION_USAGE, usage);
// save to file
std::string ident = m_IdentHash.ToBase64 ();
std::string path = m_ProfilesStorage.Path(ident);
try {
boost::property_tree::write_ini (path, pt);
} catch (std::exception& ex) {
/* boost exception verbose enough */
LogPrint (eLogError, "Profiling: ", ex.what ());
}
}
void RouterProfile::Load ()
{
std::string ident = m_IdentHash.ToBase64 ();
std::string path = m_ProfilesStorage.Path(ident);
boost::property_tree::ptree pt;
if (!i2p::fs::Exists(path)) {
LogPrint(eLogWarning, "Profiling: no profile yet for ", ident);
return;
}
try {
boost::property_tree::read_ini (path, pt);
} catch (std::exception& ex) {
/* boost exception verbose enough */
LogPrint (eLogError, "Profiling: ", ex.what ());
return;
}
try {
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
if (t.length () > 0)
m_LastUpdateTime = boost::posix_time::time_from_string (t);
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) {
try {
// read participations
auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION);
m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0);
m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0);
m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
} catch (boost::property_tree::ptree_bad_path& ex) {
LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_PARTICIPATION, " in profile for ", ident);
}
try {
// read usage
auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE);
m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
} catch (boost::property_tree::ptree_bad_path& ex) {
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident);
}
} else {
*this = RouterProfile (m_IdentHash);
}
} catch (std::exception& ex) {
LogPrint (eLogError, "Profiling: Can't read profile ", ident, " :", ex.what ());
}
}
void RouterProfile::TunnelBuildResponse (uint8_t ret)
{
UpdateTime ();
if (ret > 0)
m_NumTunnelsDeclined++;
else
m_NumTunnelsAgreed++;
}
void RouterProfile::TunnelNonReplied ()
{
m_NumTunnelsNonReplied++;
UpdateTime ();
}
bool RouterProfile::IsLowPartcipationRate () const
{
return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate
}
bool RouterProfile::IsLowReplyRate () const
{
auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined;
return m_NumTunnelsNonReplied > 10*(total + 1);
}
bool RouterProfile::IsBad ()
{
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
{
// reset profile
m_NumTunnelsAgreed = 0;
m_NumTunnelsDeclined = 0;
m_NumTunnelsNonReplied = 0;
isBad = false;
}
if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++;
return isBad;
}
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash)
{
auto profile = std::make_shared<RouterProfile> (identHash);
profile->Load (); // if possible
return profile;
}
void InitProfilesStorage ()
{
m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
}
void DeleteObsoleteProfiles ()
{
struct stat st;
std::time_t now = std::time(nullptr);
std::vector<std::string> files;
m_ProfilesStorage.Traverse(files);
for (auto path: files) {
if (stat(path.c_str(), &st) != 0) {
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
continue;
}
if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) {
LogPrint(eLogDebug, "Profiling: removing expired peer profile: ", path);
i2p::fs::Remove(path);
}
}
}
}
}

View File

@@ -1,68 +0,0 @@
#ifndef PROFILING_H__
#define PROFILING_H__
#include <memory>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Identity.h"
namespace i2p
{
namespace data
{
// sections
const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
const char PEER_PROFILE_SECTION_USAGE[] = "usage";
// params
const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime";
const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed";
const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined";
const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied";
const char PEER_PROFILE_USAGE_TAKEN[] = "taken";
const char PEER_PROFILE_USAGE_REJECTED[] = "rejected";
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days)
class RouterProfile
{
public:
RouterProfile (const IdentHash& identHash);
RouterProfile& operator= (const RouterProfile& ) = default;
void Save ();
void Load ();
bool IsBad ();
void TunnelBuildResponse (uint8_t ret);
void TunnelNonReplied ();
private:
boost::posix_time::ptime GetTime () const;
void UpdateTime ();
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
bool IsLowPartcipationRate () const;
bool IsLowReplyRate () const;
private:
IdentHash m_IdentHash;
boost::posix_time::ptime m_LastUpdateTime;
// participation
uint32_t m_NumTunnelsAgreed;
uint32_t m_NumTunnelsDeclined;
uint32_t m_NumTunnelsNonReplied;
// usage
uint32_t m_NumTimesTaken;
uint32_t m_NumTimesRejected;
};
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
void InitProfilesStorage ();
void DeleteObsoleteProfiles ();
}
}
#endif

123
Queue.h
View File

@@ -1,123 +0,0 @@
#ifndef QUEUE_H__
#define QUEUE_H__
#include <queue>
#include <vector>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <functional>
namespace i2p
{
namespace util
{
template<typename Element>
class Queue
{
public:
void Put (Element e)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
m_Queue.push (e);
m_NonEmpty.notify_one ();
}
void Put (const std::vector<Element>& vec)
{
if (!vec.empty ())
{
std::unique_lock<std::mutex> l(m_QueueMutex);
for (auto it: vec)
m_Queue.push (it);
m_NonEmpty.notify_one ();
}
}
Element GetNext ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
auto el = GetNonThreadSafe ();
if (!el)
{
m_NonEmpty.wait (l);
el = GetNonThreadSafe ();
}
return el;
}
Element GetNextWithTimeout (int usec)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
auto el = GetNonThreadSafe ();
if (!el)
{
m_NonEmpty.wait_for (l, std::chrono::milliseconds (usec));
el = GetNonThreadSafe ();
}
return el;
}
void Wait ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
m_NonEmpty.wait (l);
}
bool Wait (int sec, int usec)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_NonEmpty.wait_for (l, std::chrono::seconds (sec) + std::chrono::milliseconds (usec)) != std::cv_status::timeout;
}
bool IsEmpty ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_Queue.empty ();
}
int GetSize ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_Queue.size ();
}
void WakeUp () { m_NonEmpty.notify_all (); };
Element Get ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return GetNonThreadSafe ();
}
Element Peek ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return GetNonThreadSafe (true);
}
private:
Element GetNonThreadSafe (bool peek = false)
{
if (!m_Queue.empty ())
{
auto el = m_Queue.front ();
if (!peek)
m_Queue.pop ();
return el;
}
return nullptr;
}
private:
std::queue<Element> m_Queue;
std::mutex m_QueueMutex;
std::condition_variable m_NonEmpty;
};
}
}
#endif

View File

@@ -1,54 +1,21 @@
i2pd
====
i2pd is a full-featured C++ implementation of
[I2P](https://geti2p.net/en/about/intro) client.
I2P router written in C++.
I2P (Invisible Internet Project) is anonymous network which works on top of
public Internet. Privacy and anonymity are achieved by strong encryption and
bouncing your traffic through thousands of I2P nodes all around the world.
Documentation for config files, command line options, build instructions and more can be found under the doc directory.
We are building network which helps people to communicate and share information
without restrictions.
Downloads
------------
* [Website](http://i2pd.website)
* [Documentation](https://i2pd.readthedocs.io/en/latest/)
* [Wiki](https://github.com/PurpleI2P/i2pd/wiki)
* [Tickets/Issues](https://github.com/PurpleI2P/i2pd/issues)
* [Twitter](https://twitter.com/i2porignal)
Official binary releases could be found at:
http://download.i2p.io/purplei2p/i2pd/releases/
Installing
----------
The easiest way to install i2pd is by using
[precompiled binaries](https://github.com/PurpleI2P/i2pd/releases/latest).
See [documentation](https://i2pd.readthedocs.io/en/latest/) for how to build
i2pd from source on your OS.
Build Statuses
---------------
**Supported systems:**
* Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd)
* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd)
* Mac OS X
* FreeBSD
* Android *(coming soon)*
Using i2pd
----------
See [documentation](https://i2pd.readthedocs.io/en/latest/) and
[example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/docs/i2pd.conf).
Donations
---------
BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY
LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59
ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z
DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y
License
-------
This project is licensed under the BSD 3-clause license, which can be found in the file
LICENSE in the root of the project source code.
- Linux x64 - Maintenance
- Linux ARM - Maintenance
- Mac OS X - Maintenance
- Microsoft VC13 - To be added

View File

@@ -1,417 +0,0 @@
#include <string.h>
#include <fstream>
#include <sstream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <zlib.h>
#include "Crypto.h"
#include "I2PEndian.h"
#include "Reseed.h"
#include "FS.h"
#include "Log.h"
#include "Identity.h"
#include "NetDb.h"
#include "util.h"
namespace i2p
{
namespace data
{
static std::vector<std::string> httpsReseedHostList =
{
"https://reseed.i2p-projekt.de/", // Only HTTPS
"https://i2p.mooo.com/netDb/",
"https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required
"https://us.reseed.i2p2.no:444/",
"https://uk.reseed.i2p2.no:444/",
"https://i2p.manas.ca:8443/",
"https://i2p-0.manas.ca:8443/",
"https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support
"https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support
"https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support
};
Reseeder::Reseeder()
{
}
Reseeder::~Reseeder()
{
}
int Reseeder::ReseedNowSU3 ()
{
auto ind = rand () % httpsReseedHostList.size ();
std::string& reseedHost = httpsReseedHostList[ind];
return ReseedFromSU3 (reseedHost);
}
int Reseeder::ReseedFromSU3 (const std::string& host)
{
std::string url = host + "i2pseeds.su3";
LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", host);
std::string su3 = HttpsRequest (url);
if (su3.length () > 0)
{
std::stringstream s(su3);
return ProcessSU3Stream (s);
}
else
{
LogPrint (eLogWarning, "Reseed: SU3 download failed");
return 0;
}
}
int Reseeder::ProcessSU3File (const char * filename)
{
std::ifstream s(filename, std::ifstream::binary);
if (s.is_open ())
return ProcessSU3Stream (s);
else
{
LogPrint (eLogError, "Reseed: Can't open file ", filename);
return 0;
}
}
const char SU3_MAGIC_NUMBER[]="I2Psu3";
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50;
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008;
int Reseeder::ProcessSU3Stream (std::istream& s)
{
char magicNumber[7];
s.read (magicNumber, 7); // magic number and zero byte 6
if (strcmp (magicNumber, SU3_MAGIC_NUMBER))
{
LogPrint (eLogError, "Reseed: Unexpected SU3 magic number");
return 0;
}
s.seekg (1, std::ios::cur); // su3 file format version
SigningKeyType signatureType;
s.read ((char *)&signatureType, 2); // signature type
signatureType = be16toh (signatureType);
uint16_t signatureLength;
s.read ((char *)&signatureLength, 2); // signature length
signatureLength = be16toh (signatureLength);
s.seekg (1, std::ios::cur); // unused
uint8_t versionLength;
s.read ((char *)&versionLength, 1); // version length
s.seekg (1, std::ios::cur); // unused
uint8_t signerIDLength;
s.read ((char *)&signerIDLength, 1); // signer ID length
uint64_t contentLength;
s.read ((char *)&contentLength, 8); // content length
contentLength = be64toh (contentLength);
s.seekg (1, std::ios::cur); // unused
uint8_t fileType;
s.read ((char *)&fileType, 1); // file type
if (fileType != 0x00) // zip file
{
LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType);
return 0;
}
s.seekg (1, std::ios::cur); // unused
uint8_t contentType;
s.read ((char *)&contentType, 1); // content type
if (contentType != 0x03) // reseed data
{
LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType);
return 0;
}
s.seekg (12, std::ios::cur); // unused
s.seekg (versionLength, std::ios::cur); // skip version
char signerID[256];
s.read (signerID, signerIDLength); // signerID
signerID[signerIDLength] = 0;
//try to verify signature
auto it = m_SigningKeys.find (signerID);
if (it != m_SigningKeys.end ())
{
// TODO: implement all signature types
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096)
{
size_t pos = s.tellg ();
size_t tbsLen = pos + contentLength;
uint8_t * tbs = new uint8_t[tbsLen];
s.seekg (0, std::ios::beg);
s.read ((char *)tbs, tbsLen);
uint8_t * signature = new uint8_t[signatureLength];
s.read ((char *)signature, signatureLength);
// RSA-raw
{
// calculate digest
uint8_t digest[64];
SHA512 (tbs, tbsLen, digest);
// encrypt signature
BN_CTX * bnctx = BN_CTX_new ();
BIGNUM * s = BN_new (), * n = BN_new ();
BN_bin2bn (signature, signatureLength, s);
BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n);
BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n
uint8_t * enSigBuf = new uint8_t[signatureLength];
i2p::crypto::bn2buf (s, enSigBuf, signatureLength);
// digest is right aligned
// we can't use RSA_verify due wrong padding in SU3
if (memcmp (enSigBuf + (signatureLength - 64), digest, 64))
LogPrint (eLogWarning, "Reseed: SU3 signature verification failed");
delete[] enSigBuf;
BN_free (s); BN_free (n);
BN_CTX_free (bnctx);
}
delete[] signature;
delete[] tbs;
s.seekg (pos, std::ios::beg);
}
else
LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported");
}
else
LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded");
// handle content
int numFiles = 0;
size_t contentPos = s.tellg ();
while (!s.eof ())
{
uint32_t signature;
s.read ((char *)&signature, 4);
signature = le32toh (signature);
if (signature == ZIP_HEADER_SIGNATURE)
{
// next local file
s.seekg (2, std::ios::cur); // version
uint16_t bitFlag;
s.read ((char *)&bitFlag, 2);
bitFlag = le16toh (bitFlag);
uint16_t compressionMethod;
s.read ((char *)&compressionMethod, 2);
compressionMethod = le16toh (compressionMethod);
s.seekg (4, std::ios::cur); // skip fields we don't care about
uint32_t compressedSize, uncompressedSize;
uint32_t crc_32;
s.read ((char *)&crc_32, 4);
crc_32 = le32toh (crc_32);
s.read ((char *)&compressedSize, 4);
compressedSize = le32toh (compressedSize);
s.read ((char *)&uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
uint16_t fileNameLength, extraFieldLength;
s.read ((char *)&fileNameLength, 2);
fileNameLength = le16toh (fileNameLength);
if ( fileNameLength > 255 ) {
// too big
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength);
return numFiles;
}
s.read ((char *)&extraFieldLength, 2);
extraFieldLength = le16toh (extraFieldLength);
char localFileName[255];
s.read (localFileName, fileNameLength);
localFileName[fileNameLength] = 0;
s.seekg (extraFieldLength, std::ios::cur);
// take care about data desriptor if presented
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
{
size_t pos = s.tellg ();
if (!FindZipDataDescriptor (s))
{
LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found");
return numFiles;
}
s.read ((char *)&crc_32, 4);
crc_32 = le32toh (crc_32);
s.read ((char *)&compressedSize, 4);
compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data
s.read ((char *)&uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
// now we know compressed and uncompressed size
s.seekg (pos, std::ios::beg); // back to compressed data
}
LogPrint (eLogDebug, "Reseed: Proccessing file ", localFileName, " ", compressedSize, " bytes");
if (!compressedSize)
{
LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped");
continue;
}
uint8_t * compressed = new uint8_t[compressedSize];
s.read ((char *)compressed, compressedSize);
if (compressionMethod) // we assume Deflate
{
z_stream inflator;
memset (&inflator, 0, sizeof (inflator));
inflateInit2 (&inflator, -MAX_WBITS); // no zlib header
uint8_t * uncompressed = new uint8_t[uncompressedSize];
inflator.next_in = compressed;
inflator.avail_in = compressedSize;
inflator.next_out = uncompressed;
inflator.avail_out = uncompressedSize;
int err;
if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0)
{
uncompressedSize -= inflator.avail_out;
if (crc32 (0, uncompressed, uncompressedSize) == crc_32)
{
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize);
numFiles++;
}
else
LogPrint (eLogError, "Reseed: CRC32 verification failed");
}
else
LogPrint (eLogError, "Reseed: SU3 decompression error ", err);
delete[] uncompressed;
inflateEnd (&inflator);
}
else // no compression
{
i2p::data::netdb.AddRouterInfo (compressed, compressedSize);
numFiles++;
}
delete[] compressed;
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4)
}
else
{
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
LogPrint (eLogWarning, "Reseed: Missing zip central directory header");
break; // no more files
}
size_t end = s.tellg ();
if (end - contentPos >= contentLength)
break; // we are beyond contentLength
}
return numFiles;
}
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 };
bool Reseeder::FindZipDataDescriptor (std::istream& s)
{
size_t nextInd = 0;
while (!s.eof ())
{
uint8_t nextByte;
s.read ((char *)&nextByte, 1);
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd])
{
nextInd++;
if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE))
return true;
}
else
nextInd = 0;
}
return false;
}
void Reseeder::LoadCertificate (const std::string& filename)
{
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
if (ret)
{
SSL * ssl = SSL_new (ctx);
X509 * cert = SSL_get_certificate (ssl);
// verify
if (cert)
{
// extract issuer name
char name[100];
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
// extract RSA key (we need n only, e = 65537)
RSA * key = X509_get_pubkey (cert)->pkey.rsa;
PublicKey value;
i2p::crypto::bn2buf (key->n, value, 512);
m_SigningKeys[name] = value;
}
SSL_free (ssl);
}
else
LogPrint (eLogError, "Reseed: Can't open certificate file ", filename);
SSL_CTX_free (ctx);
}
void Reseeder::LoadCertificates ()
{
std::string certDir = i2p::fs::DataDirPath("certificates", "reseed");
std::vector<std::string> files;
int numCertificates = 0;
if (!i2p::fs::ReadDir(certDir, files)) {
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir);
return;
}
for (const std::string & file : files) {
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
LogPrint(eLogWarning, "Reseed: ignoring file ", file);
continue;
}
LoadCertificate (file);
numCertificates++;
}
LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded");
}
std::string Reseeder::HttpsRequest (const std::string& address)
{
i2p::util::http::url u(address);
if (u.port_ == 80) u.port_ = 443;
boost::asio::io_service service;
boost::system::error_code ecode;
auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode);
if (!ecode)
{
boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23);
ctx.set_verify_mode(boost::asio::ssl::context::verify_none);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> s(service, ctx);
s.lowest_layer().connect (*it, ecode);
if (!ecode)
{
s.handshake (boost::asio::ssl::stream_base::client, ecode);
if (!ecode)
{
LogPrint (eLogInfo, "Reseed: Connected to ", u.host_, ":", u.port_);
// send request
std::stringstream ss;
ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n";
s.write_some (boost::asio::buffer (ss.str ()));
// read response
std::stringstream rs;
char response[1024]; size_t l = 0;
do
{
l = s.read_some (boost::asio::buffer (response, 1024), ecode);
if (l) rs.write (response, l);
}
while (!ecode && l);
// process response
return i2p::util::http::GetHttpContent (rs);
}
else
LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ());
}
else
LogPrint (eLogError, "Reseed: Couldn't connect to ", u.host_, ": ", ecode.message ());
}
else
LogPrint (eLogError, "Reseed: Couldn't resolve address ", u.host_, ": ", ecode.message ());
return "";
}
}
}

View File

@@ -1,47 +0,0 @@
#ifndef RESEED_H
#define RESEED_H
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include "Identity.h"
#include "Crypto.h"
namespace i2p
{
namespace data
{
class Reseeder
{
typedef Tag<512> PublicKey;
public:
Reseeder();
~Reseeder();
int ReseedNowSU3 ();
void LoadCertificates ();
private:
void LoadCertificate (const std::string& filename);
int ReseedFromSU3 (const std::string& host);
int ProcessSU3File (const char * filename);
int ProcessSU3Stream (std::istream& s);
bool FindZipDataDescriptor (std::istream& s);
std::string HttpsRequest (const std::string& address);
private:
std::map<std::string, PublicKey> m_SigningKeys;
};
}
}
#endif

View File

@@ -1,425 +0,0 @@
#include <fstream>
#include <boost/lexical_cast.hpp>
#include "Config.h"
#include "Crypto.h"
#include "Timestamp.h"
#include "I2NPProtocol.h"
#include "NetDb.h"
#include "FS.h"
#include "util.h"
#include "version.h"
#include "Log.h"
#include "Family.h"
#include "RouterContext.h"
namespace i2p
{
RouterContext context;
RouterContext::RouterContext ():
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
m_StartupTime (0), m_Status (eRouterStatusOK )
{
}
void RouterContext::Init ()
{
srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
m_StartupTime = i2p::util::GetSecondsSinceEpoch ();
if (!Load ())
CreateNewRouter ();
UpdateRouterInfo ();
}
void RouterContext::CreateNewRouter ()
{
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519);
#else
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1);
#endif
SaveKeys ();
NewRouterInfo ();
}
void RouterContext::NewRouterInfo ()
{
i2p::data::RouterInfo routerInfo;
routerInfo.SetRouterIdentity (GetIdentity ());
uint16_t port; i2p::config::GetOption("port", port);
if (!port)
port = rand () % (30777 - 9111) + 9111; // I2P network ports range
std::string host; i2p::config::GetOption("host", host);
if (i2p::config::IsDefault("host"))
host = "127.0.0.1"; // replace default address with safe value
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ());
routerInfo.AddNTCPAddress (host.c_str(), port);
routerInfo.SetCaps (i2p::data::RouterInfo::eReachable |
i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC
routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID));
routerInfo.SetProperty ("router.version", I2P_VERSION);
routerInfo.CreateBuffer (m_Keys);
m_RouterInfo.SetRouterIdentity (GetIdentity ());
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
}
void RouterContext::UpdateRouterInfo ()
{
m_RouterInfo.CreateBuffer (m_Keys);
m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
}
void RouterContext::SetStatus (RouterStatus status)
{
if (status != m_Status)
{
m_Status = status;
switch (m_Status)
{
case eRouterStatusOK:
SetReachable ();
break;
case eRouterStatusFirewalled:
SetUnreachable ();
break;
default:
;
}
}
}
void RouterContext::UpdatePort (int port)
{
bool updated = false;
for (auto address : m_RouterInfo.GetAddresses ())
{
if (address->port != port)
{
address->port = port;
updated = true;
}
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateAddress (const boost::asio::ip::address& host)
{
bool updated = false;
for (auto address : m_RouterInfo.GetAddresses ())
{
if (address->host != host && address->IsCompatible (host))
{
address->host = host;
updated = true;
}
}
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
UpdateRouterInfo ();
}
bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer)
{
bool ret = m_RouterInfo.AddIntroducer (introducer);
if (ret)
UpdateRouterInfo ();
return ret;
}
void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{
if (m_RouterInfo.RemoveIntroducer (e))
UpdateRouterInfo ();
}
void RouterContext::SetFloodfill (bool floodfill)
{
m_IsFloodfill = floodfill;
if (floodfill)
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill);
else
{
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
// we don't publish number of routers and leaseset for non-floodfill
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS);
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS);
}
UpdateRouterInfo ();
}
std::string RouterContext::GetFamily () const
{
return m_RouterInfo.GetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY);
}
void RouterContext::SetFamily (const std::string& family)
{
std::string signature;
if (family.length () > 0)
signature = i2p::data::CreateFamilySignature (family, GetIdentHash ());
if (signature.length () > 0)
{
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family);
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG, signature);
}
else
{
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY);
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG);
}
}
void RouterContext::SetBandwidth (char L) {
uint16_t limit = 0;
enum { low, high, extra } type = high;
/* detect parameters */
switch (L)
{
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break;
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break;
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break;
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 9999; type = extra; break;
default:
limit = 48; type = low;
}
/* update caps & flags in RI */
auto caps = m_RouterInfo.GetCaps ();
caps &= ~i2p::data::RouterInfo::eHighBandwidth;
caps &= ~i2p::data::RouterInfo::eExtraBandwidth;
switch (type)
{
case low : /* not set */; break;
case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break;
case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break;
}
m_RouterInfo.SetCaps (caps);
UpdateRouterInfo ();
m_BandwidthLimit = limit;
}
void RouterContext::SetBandwidth (int limit)
{
if (limit > 2000) { SetBandwidth('X'); }
else if (limit > 256) { SetBandwidth('P'); }
else if (limit > 128) { SetBandwidth('O'); }
else if (limit > 64) { SetBandwidth('N'); }
else if (limit > 48) { SetBandwidth('M'); }
else if (limit > 12) { SetBandwidth('L'); }
else { SetBandwidth('K'); }
}
bool RouterContext::IsUnreachable () const
{
return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable;
}
void RouterContext::SetUnreachable ()
{
// set caps
m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B
// remove NTCP address
auto& addresses = m_RouterInfo.GetAddresses ();
for (size_t i = 0; i < addresses.size (); i++)
{
if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{
addresses.erase (addresses.begin () + i);
break;
}
}
// delete previous introducers
for (auto addr : addresses)
addr->introducers.clear ();
// update
UpdateRouterInfo ();
}
void RouterContext::SetReachable ()
{
// update caps
uint8_t caps = m_RouterInfo.GetCaps ();
caps &= ~i2p::data::RouterInfo::eUnreachable;
caps |= i2p::data::RouterInfo::eReachable;
caps |= i2p::data::RouterInfo::eSSUIntroducer;
if (m_IsFloodfill)
caps |= i2p::data::RouterInfo::eFloodfill;
m_RouterInfo.SetCaps (caps);
// insert NTCP back
auto& addresses = m_RouterInfo.GetAddresses ();
for (size_t i = 0; i < addresses.size (); i++)
{
if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU)
{
// insert NTCP address with host/port from SSU
m_RouterInfo.AddNTCPAddress (addresses[i]->host.to_string ().c_str (), addresses[i]->port);
break;
}
}
// delete previous introducers
for (auto addr : addresses)
addr->introducers.clear ();
// update
UpdateRouterInfo ();
}
void RouterContext::SetSupportsV6 (bool supportsV6)
{
if (supportsV6)
m_RouterInfo.EnableV6 ();
else
m_RouterInfo.DisableV6 ();
UpdateRouterInfo ();
}
void RouterContext::SetSupportsV4 (bool supportsV4)
{
if (supportsV4)
m_RouterInfo.EnableV4 ();
else
m_RouterInfo.DisableV4 ();
UpdateRouterInfo ();
}
void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host)
{
bool updated = false, found = false;
int port = 0;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto addr: addresses)
{
if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
{
if (addr->host != host)
{
addr->host = host;
updated = true;
}
found = true;
}
else
port = addr->port;
}
if (!found)
{
// create new address
m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port);
auto mtu = i2p::util::net::GetMTU (host);
if (mtu)
{
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
if (mtu > 1472) { // TODO: magic constant
mtu = 1472;
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes");
}
}
m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO
updated = true;
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateStats ()
{
if (m_IsFloodfill)
{
// update routers and leasesets
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast<std::string>(i2p::data::netdb.GetNumLeaseSets ()));
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast<std::string>(i2p::data::netdb.GetNumRouters ()));
UpdateRouterInfo ();
}
}
bool RouterContext::Load ()
{
std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary);
if (!fk.is_open ()) return false;
fk.seekg (0, std::ios::end);
size_t len = fk.tellg();
fk.seekg (0, std::ios::beg);
if (len == sizeof (i2p::data::Keys)) // old keys file format
{
i2p::data::Keys keys;
fk.read ((char *)&keys, sizeof (keys));
m_Keys = keys;
}
else // new keys file format
{
uint8_t * buf = new uint8_t[len];
fk.read ((char *)buf, len);
m_Keys.FromBuffer (buf, len);
delete[] buf;
}
m_RouterInfo.SetRouterIdentity (GetIdentity ());
i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO));
if (!routerInfo.IsUnreachable ()) // router.info looks good
{
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION);
m_RouterInfo.SetProperty ("router.version", I2P_VERSION);
// Migration to 0.9.24. TODO: remove later
m_RouterInfo.DeleteProperty ("coreVersion");
m_RouterInfo.DeleteProperty ("stat_uptime");
}
else
{
LogPrint (eLogError, ROUTER_INFO, " is malformed. Creating new");
NewRouterInfo ();
}
if (IsUnreachable ())
SetReachable (); // we assume reachable until we discover firewall through peer tests
return true;
}
void RouterContext::SaveKeys ()
{
// save in the same format as .dat files
std::ofstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ofstream::binary | std::ofstream::out);
size_t len = m_Keys.GetFullLen ();
uint8_t * buf = new uint8_t[len];
m_Keys.ToBuffer (buf, len);
fk.write ((char *)buf, len);
delete[] buf;
}
std::shared_ptr<i2p::tunnel::TunnelPool> RouterContext::GetTunnelPool () const
{
return i2p::tunnel::tunnels.GetExploratoryPool ();
}
void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
}
void RouterContext::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg);
}
void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
}
uint32_t RouterContext::GetUptime () const
{
return i2p::util::GetSecondsSinceEpoch () - m_StartupTime;
}
}

View File

@@ -1,115 +0,0 @@
#ifndef ROUTER_CONTEXT_H__
#define ROUTER_CONTEXT_H__
#include <inttypes.h>
#include <string>
#include <memory>
#include <mutex>
#include <boost/asio.hpp>
#include "Identity.h"
#include "RouterInfo.h"
#include "Garlic.h"
namespace i2p
{
const char ROUTER_INFO[] = "router.info";
const char ROUTER_KEYS[] = "router.keys";
const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes
enum RouterStatus
{
eRouterStatusOK = 0,
eRouterStatusTesting = 1,
eRouterStatusFirewalled = 2
};
class RouterContext: public i2p::garlic::GarlicDestination
{
public:
RouterContext ();
void Init ();
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; };
std::shared_ptr<const i2p::data::RouterInfo> GetSharedRouterInfo () const
{
return std::shared_ptr<const i2p::data::RouterInfo> (&m_RouterInfo,
[](const i2p::data::RouterInfo *) {});
}
std::shared_ptr<i2p::garlic::GarlicDestination> GetSharedDestination ()
{
return std::shared_ptr<i2p::garlic::GarlicDestination> (this,
[](i2p::garlic::GarlicDestination *) {});
}
uint32_t GetUptime () const;
uint32_t GetStartupTime () const { return m_StartupTime; };
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; };
RouterStatus GetStatus () const { return m_Status; };
void SetStatus (RouterStatus status);
void UpdatePort (int port); // called from Daemon
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer);
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
bool IsUnreachable () const;
void SetUnreachable ();
void SetReachable ();
bool IsFloodfill () const { return m_IsFloodfill; };
void SetFloodfill (bool floodfill);
void SetFamily (const std::string& family);
std::string GetFamily () const;
void SetBandwidth (int limit); /* in kilobytes */
void SetBandwidth (char L); /* by letter */
bool AcceptsTunnels () const { return m_AcceptsTunnels; };
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
bool SupportsV4 () const { return m_RouterInfo.IsV4 (); };
void SetSupportsV6 (bool supportsV6);
void SetSupportsV4 (bool supportsV4);
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
void UpdateStats ();
// implements LocalDestination
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); };
const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetStandardIdentity ().publicKey; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
void SetLeaseSetUpdated () {};
// implements GarlicDestination
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () { return nullptr; };
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const;
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
// override GarlicDestination
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
private:
void CreateNewRouter ();
void NewRouterInfo ();
void UpdateRouterInfo ();
bool Load ();
void SaveKeys ();
private:
i2p::data::RouterInfo m_RouterInfo;
i2p::data::PrivateKeys m_Keys;
uint64_t m_LastUpdateTime;
bool m_AcceptsTunnels, m_IsFloodfill;
uint64_t m_StartupTime; // in seconds since epoch
uint32_t m_BandwidthLimit; // allowed bandwidth
RouterStatus m_Status;
std::mutex m_GarlicMutex;
};
extern RouterContext context;
}
#endif

View File

@@ -1,787 +0,0 @@
#include <stdio.h>
#include <string.h>
#include "I2PEndian.h"
#include <fstream>
#include <boost/lexical_cast.hpp>
#include "version.h"
#include "Crypto.h"
#include "Base.h"
#include "Timestamp.h"
#include "Log.h"
#include "NetDb.h"
#include "RouterInfo.h"
namespace i2p
{
namespace data
{
RouterInfo::RouterInfo (const std::string& fullPath):
m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false),
m_SupportedTransports (0), m_Caps (0)
{
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
ReadFromFile ();
}
RouterInfo::RouterInfo (const uint8_t * buf, int len):
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
{
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
ReadFromBuffer (true);
}
RouterInfo::~RouterInfo ()
{
delete[] m_Buffer;
}
void RouterInfo::Update (const uint8_t * buf, int len)
{
// verify signature since we have indentity already
int l = len - m_RouterIdentity->GetSignatureLen ();
if (m_RouterIdentity->Verify (buf, l, buf + l))
{
// clean up
m_IsUpdated = true;
m_IsUnreachable = false;
m_SupportedTransports = 0;
m_Caps = 0;
m_Addresses.clear ();
m_Properties.clear ();
// copy buffer
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
// skip identity
size_t identityLen = m_RouterIdentity->GetFullLen ();
// read new RI
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
ReadFromStream (str);
// don't delete buffer until saved to the file
}
else
{
LogPrint (eLogError, "RouterInfo: signature verification failed");
m_IsUnreachable = true;
}
}
void RouterInfo::SetRouterIdentity (std::shared_ptr<const IdentityEx> identity)
{
m_RouterIdentity = identity;
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch ();
}
bool RouterInfo::LoadFile ()
{
std::ifstream s(m_FullPath, std::ifstream::binary);
if (s.is_open ())
{
s.seekg (0,std::ios::end);
m_BufferLen = s.tellg ();
if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE)
{
LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed");
return false;
}
s.seekg(0, std::ios::beg);
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
s.read((char *)m_Buffer, m_BufferLen);
}
else
{
LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath);
return false;
}
return true;
}
void RouterInfo::ReadFromFile ()
{
if (LoadFile ())
ReadFromBuffer (false);
else
m_IsUnreachable = true;
}
void RouterInfo::ReadFromBuffer (bool verifySignature)
{
m_RouterIdentity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen);
size_t identityLen = m_RouterIdentity->GetFullLen ();
if (identityLen >= m_BufferLen)
{
LogPrint (eLogError, "RouterInfo: identity length ", identityLen, " exceeds buffer size ", m_BufferLen);
m_IsUnreachable = true;
return;
}
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
ReadFromStream (str);
if (!str)
{
LogPrint (eLogError, "RouterInfo: malformed message");
m_IsUnreachable = true;
return;
}
if (verifySignature)
{
// verify signature
int l = m_BufferLen - m_RouterIdentity->GetSignatureLen ();
if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l))
{
LogPrint (eLogError, "RouterInfo: signature verification failed");
m_IsUnreachable = true;
}
m_RouterIdentity->DropVerifier ();
}
}
void RouterInfo::ReadFromStream (std::istream& s)
{
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
m_Timestamp = be64toh (m_Timestamp);
// read addresses
uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return;
bool introducers = false;
for (int i = 0; i < numAddresses; i++)
{
uint8_t supportedTransports = 0;
bool isValidAddress = true;
Address address;
s.read ((char *)&address.cost, sizeof (address.cost));
s.read ((char *)&address.date, sizeof (address.date));
char transportStyle[5];
ReadString (transportStyle, s);
if (!strcmp (transportStyle, "NTCP"))
address.transportStyle = eTransportNTCP;
else if (!strcmp (transportStyle, "SSU"))
address.transportStyle = eTransportSSU;
else
address.transportStyle = eTransportUnknown;
address.port = 0;
address.mtu = 0;
uint16_t size, r = 0;
s.read ((char *)&size, sizeof (size)); if (!s) return;
size = be16toh (size);
while (r < size)
{
char key[500], value[500];
r += ReadString (key, s);
s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!strcmp (key, "host"))
{
boost::system::error_code ecode;
address.host = boost::asio::ip::address::from_string (value, ecode);
if (ecode)
{
if (address.transportStyle == eTransportNTCP)
{
supportedTransports |= eNTCPV4; // TODO:
address.addressString = value;
}
else
{
supportedTransports |= eSSUV4; // TODO:
address.addressString = value;
}
}
else
{
// add supported protocol
if (address.host.is_v4 ())
supportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4;
else
supportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6;
}
}
else if (!strcmp (key, "port"))
address.port = boost::lexical_cast<int>(value);
else if (!strcmp (key, "mtu"))
address.mtu = boost::lexical_cast<int>(value);
else if (!strcmp (key, "key"))
Base64ToByteStream (value, strlen (value), address.key, 32);
else if (!strcmp (key, "caps"))
ExtractCaps (value);
else if (key[0] == 'i')
{
// introducers
introducers = true;
size_t l = strlen(key);
unsigned char index = key[l-1] - '0'; // TODO:
key[l-1] = 0;
if (index >= address.introducers.size ())
address.introducers.resize (index + 1);
Introducer& introducer = address.introducers.at (index);
if (!strcmp (key, "ihost"))
{
boost::system::error_code ecode;
introducer.iHost = boost::asio::ip::address::from_string (value, ecode);
}
else if (!strcmp (key, "iport"))
introducer.iPort = boost::lexical_cast<int>(value);
else if (!strcmp (key, "itag"))
introducer.iTag = boost::lexical_cast<uint32_t>(value);
else if (!strcmp (key, "ikey"))
Base64ToByteStream (value, strlen (value), introducer.iKey, 32);
}
if (!s) return;
}
if (isValidAddress)
{
m_Addresses.push_back(std::make_shared<Address>(address));
m_SupportedTransports |= supportedTransports;
}
}
// read peers
uint8_t numPeers;
s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
// read properties
uint16_t size, r = 0;
s.read ((char *)&size, sizeof (size)); if (!s) return;
size = be16toh (size);
while (r < size)
{
#ifdef _WIN32
char key[500], value[500];
// TODO: investigate why properties get read as one long string under Windows
// length should not be more than 44
#else
char key[50], value[50];
#endif
r += ReadString (key, s);
s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, s);
s.seekg (1, std::ios_base::cur); r++; // ;
m_Properties[key] = value;
// extract caps
if (!strcmp (key, "caps"))
ExtractCaps (value);
// check netId
else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != I2PD_NET_ID)
{
LogPrint (eLogError, "Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
m_IsUnreachable = true;
}
// family
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
{
m_Family = value;
boost::to_lower (m_Family);
}
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
{
if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value))
{
LogPrint (eLogWarning, "RouterInfo: family signature verification failed");
m_Family.clear ();
}
}
if (!s) return;
}
if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers))
SetUnreachable (true);
}
void RouterInfo::ExtractCaps (const char * value)
{
const char * cap = value;
while (*cap)
{
switch (*cap)
{
case CAPS_FLAG_FLOODFILL:
m_Caps |= Caps::eFloodfill;
break;
case CAPS_FLAG_HIGH_BANDWIDTH1:
case CAPS_FLAG_HIGH_BANDWIDTH2:
case CAPS_FLAG_HIGH_BANDWIDTH3:
m_Caps |= Caps::eHighBandwidth;
break;
case CAPS_FLAG_EXTRA_BANDWIDTH1:
case CAPS_FLAG_EXTRA_BANDWIDTH2:
m_Caps |= Caps::eExtraBandwidth;
break;
case CAPS_FLAG_HIDDEN:
m_Caps |= Caps::eHidden;
break;
case CAPS_FLAG_REACHABLE:
m_Caps |= Caps::eReachable;
break;
case CAPS_FLAG_UNREACHABLE:
m_Caps |= Caps::eUnreachable;
break;
case CAPS_FLAG_SSU_TESTING:
m_Caps |= Caps::eSSUTesting;
break;
case CAPS_FLAG_SSU_INTRODUCER:
m_Caps |= Caps::eSSUIntroducer;
break;
default: ;
}
cap++;
}
}
void RouterInfo::UpdateCapsProperty ()
{
std::string caps;
if (m_Caps & eFloodfill) {
caps += CAPS_FLAG_FLOODFILL; // floodfill
caps += (m_Caps & eExtraBandwidth)
? CAPS_FLAG_EXTRA_BANDWIDTH1 // 'P'
: CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
} else {
if (m_Caps & eExtraBandwidth) {
caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P'
} else if (m_Caps & eHighBandwidth) {
caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
} else {
caps += CAPS_FLAG_LOW_BANDWIDTH2; // 'L'
}
}
if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable
SetProperty ("caps", caps);
}
void RouterInfo::WriteToStream (std::ostream& s)
{
uint64_t ts = htobe64 (m_Timestamp);
s.write ((char *)&ts, sizeof (ts));
// addresses
uint8_t numAddresses = m_Addresses.size ();
s.write ((char *)&numAddresses, sizeof (numAddresses));
for (auto addr : m_Addresses)
{
Address& address = *addr;
s.write ((char *)&address.cost, sizeof (address.cost));
s.write ((char *)&address.date, sizeof (address.date));
std::stringstream properties;
if (address.transportStyle == eTransportNTCP)
WriteString ("NTCP", s);
else if (address.transportStyle == eTransportSSU)
{
WriteString ("SSU", s);
// caps
WriteString ("caps", properties);
properties << '=';
std::string caps;
if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
WriteString (caps, properties);
properties << ';';
}
else
WriteString ("", s);
WriteString ("host", properties);
properties << '=';
WriteString (address.host.to_string (), properties);
properties << ';';
if (address.transportStyle == eTransportSSU)
{
// write introducers if any
if (address.introducers.size () > 0)
{
int i = 0;
for (auto introducer: address.introducers)
{
WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (introducer.iHost.to_string (), properties);
properties << ';';
i++;
}
i = 0;
for (auto introducer: address.introducers)
{
WriteString ("ikey" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
char value[64];
size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64);
value[l] = 0;
WriteString (value, properties);
properties << ';';
i++;
}
i = 0;
for (auto introducer: address.introducers)
{
WriteString ("iport" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iPort), properties);
properties << ';';
i++;
}
i = 0;
for (auto introducer: address.introducers)
{
WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
properties << ';';
i++;
}
}
// write intro key
WriteString ("key", properties);
properties << '=';
char value[64];
size_t l = ByteStreamToBase64 (address.key, 32, value, 64);
value[l] = 0;
WriteString (value, properties);
properties << ';';
// write mtu
if (address.mtu)
{
WriteString ("mtu", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.mtu), properties);
properties << ';';
}
}
WriteString ("port", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.port), properties);
properties << ';';
uint16_t size = htobe16 (properties.str ().size ());
s.write ((char *)&size, sizeof (size));
s.write (properties.str ().c_str (), properties.str ().size ());
}
// peers
uint8_t numPeers = 0;
s.write ((char *)&numPeers, sizeof (numPeers));
// properties
std::stringstream properties;
for (auto& p : m_Properties)
{
WriteString (p.first, properties);
properties << '=';
WriteString (p.second, properties);
properties << ';';
}
uint16_t size = htobe16 (properties.str ().size ());
s.write ((char *)&size, sizeof (size));
s.write (properties.str ().c_str (), properties.str ().size ());
}
bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const
{
if (!m_RouterIdentity) return false;
size_t size = m_RouterIdentity->GetFullLen ();
if (size + 8 > len) return false;
return bufbe64toh (buf + size) > m_Timestamp;
}
const uint8_t * RouterInfo::LoadBuffer ()
{
if (!m_Buffer)
{
if (LoadFile ())
LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file");
}
return m_Buffer;
}
void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys)
{
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp
std::stringstream s;
uint8_t ident[1024];
auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024);
s.write ((char *)ident, identLen);
WriteToStream (s);
m_BufferLen = s.str ().size ();
if (!m_Buffer)
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, s.str ().c_str (), m_BufferLen);
// signature
privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen);
m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen ();
}
bool RouterInfo::SaveToFile (const std::string& fullPath)
{
m_FullPath = fullPath;
if (!m_Buffer) {
LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL");
return false;
}
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
if (!f.is_open ()) {
LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath);
return false;
}
f.write ((char *)m_Buffer, m_BufferLen);
return true;
}
size_t RouterInfo::ReadString (char * str, std::istream& s)
{
uint8_t len;
s.read ((char *)&len, 1);
s.read (str, len);
str[len] = 0;
return len+1;
}
void RouterInfo::WriteString (const std::string& str, std::ostream& s)
{
uint8_t len = str.size ();
s.write ((char *)&len, 1);
s.write (str.c_str (), len);
}
void RouterInfo::AddNTCPAddress (const char * host, int port)
{
auto addr = std::make_shared<Address>();
addr->host = boost::asio::ip::address::from_string (host);
addr->port = port;
addr->transportStyle = eTransportNTCP;
addr->cost = 2;
addr->date = 0;
addr->mtu = 0;
for (auto it: m_Addresses) // don't insert same address twice
if (*it == *addr) return;
m_Addresses.push_back(addr);
m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4;
}
void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu)
{
auto addr = std::make_shared<Address>();
addr->host = boost::asio::ip::address::from_string (host);
addr->port = port;
addr->transportStyle = eTransportSSU;
addr->cost = 10; // NTCP should have priority over SSU
addr->date = 0;
addr->mtu = mtu;
memcpy (addr->key, key, 32);
for (auto it: m_Addresses) // don't insert same address twice
if (*it == *addr) return;
m_Addresses.push_back(addr);
m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
m_Caps |= eSSUTesting;
m_Caps |= eSSUIntroducer;
}
bool RouterInfo::AddIntroducer (const Introducer& introducer)
{
for (auto addr : m_Addresses)
{
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
{
for (auto intro: addr->introducers)
if (intro.iTag == introducer.iTag) return false; // already presented
addr->introducers.push_back (introducer);
return true;
}
}
return false;
}
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{
for (auto addr: m_Addresses)
{
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
{
for (std::vector<Introducer>::iterator it = addr->introducers.begin (); it != addr->introducers.end (); it++)
if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e)
{
addr->introducers.erase (it);
return true;
}
}
}
return false;
}
void RouterInfo::SetCaps (uint8_t caps)
{
m_Caps = caps;
UpdateCapsProperty ();
}
void RouterInfo::SetCaps (const char * caps)
{
SetProperty ("caps", caps);
m_Caps = 0;
ExtractCaps (caps);
}
void RouterInfo::SetProperty (const std::string& key, const std::string& value)
{
m_Properties[key] = value;
}
void RouterInfo::DeleteProperty (const std::string& key)
{
m_Properties.erase (key);
}
std::string RouterInfo::GetProperty (const std::string& key) const
{
auto it = m_Properties.find (key);
if (it != m_Properties.end ())
return it->second;
return "";
}
bool RouterInfo::IsNTCP (bool v4only) const
{
if (v4only)
return m_SupportedTransports & eNTCPV4;
else
return m_SupportedTransports & (eNTCPV4 | eNTCPV6);
}
bool RouterInfo::IsSSU (bool v4only) const
{
if (v4only)
return m_SupportedTransports & eSSUV4;
else
return m_SupportedTransports & (eSSUV4 | eSSUV6);
}
bool RouterInfo::IsV6 () const
{
return m_SupportedTransports & (eNTCPV6 | eSSUV6);
}
bool RouterInfo::IsV4 () const
{
return m_SupportedTransports & (eNTCPV4 | eSSUV4);
}
void RouterInfo::EnableV6 ()
{
if (!IsV6 ())
m_SupportedTransports |= eNTCPV6 | eSSUV6;
}
void RouterInfo::EnableV4 ()
{
if (!IsV4 ())
m_SupportedTransports |= eNTCPV4 | eSSUV4;
}
void RouterInfo::DisableV6 ()
{
if (IsV6 ())
{
// NTCP
m_SupportedTransports &= ~eNTCPV6;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
m_Addresses[i]->host.is_v6 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
// SSU
m_SupportedTransports &= ~eSSUV6;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
m_Addresses[i]->host.is_v6 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
}
}
void RouterInfo::DisableV4 ()
{
if (IsV4 ())
{
// NTCP
m_SupportedTransports &= ~eNTCPV4;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
m_Addresses[i]->host.is_v4 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
// SSU
m_SupportedTransports &= ~eSSUV4;
for (size_t i = 0; i < m_Addresses.size (); i++)
{
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
m_Addresses[i]->host.is_v4 ())
{
m_Addresses.erase (m_Addresses.begin () + i);
break;
}
}
}
}
bool RouterInfo::UsesIntroducer () const
{
return m_Caps & Caps::eUnreachable; // non-reachable
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetNTCPAddress (bool v4only) const
{
return GetAddress (eTransportNTCP, v4only);
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
{
return GetAddress (eTransportSSU, v4only);
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUV6Address () const
{
return GetAddress (eTransportSSU, false, true);
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const
{
for (auto address : m_Addresses)
{
if (address->transportStyle == s)
{
if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ()))
return address;
}
}
return nullptr;
}
std::shared_ptr<RouterProfile> RouterInfo::GetProfile () const
{
if (!m_Profile)
m_Profile = GetRouterProfile (GetIdentHash ());
return m_Profile;
}
}
}

View File

@@ -1,208 +0,0 @@
#ifndef ROUTER_INFO_H__
#define ROUTER_INFO_H__
#include <inttypes.h>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <boost/asio.hpp>
#include "Identity.h"
#include "Profiling.h"
namespace i2p
{
namespace data
{
const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets";
const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters";
const char ROUTER_INFO_PROPERTY_NETID[] = "netId";
const char ROUTER_INFO_PROPERTY_FAMILY[] = "family";
const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig";
const char CAPS_FLAG_FLOODFILL = 'f';
const char CAPS_FLAG_HIDDEN = 'H';
const char CAPS_FLAG_REACHABLE = 'R';
const char CAPS_FLAG_UNREACHABLE = 'U';
/* bandwidth flags */
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */
const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */
const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */
const char CAPS_FLAG_SSU_TESTING = 'B';
const char CAPS_FLAG_SSU_INTRODUCER = 'C';
const int MAX_RI_BUFFER_SIZE = 2048;
class RouterInfo: public RoutingDestination
{
public:
enum SupportedTranports
{
eNTCPV4 = 0x01,
eNTCPV6 = 0x02,
eSSUV4 = 0x04,
eSSUV6 = 0x08
};
enum Caps
{
eFloodfill = 0x01,
eHighBandwidth = 0x02,
eExtraBandwidth = 0x04,
eReachable = 0x08,
eSSUTesting = 0x10,
eSSUIntroducer = 0x20,
eHidden = 0x40,
eUnreachable = 0x80
};
enum TransportStyle
{
eTransportUnknown = 0,
eTransportNTCP,
eTransportSSU
};
typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey
struct Introducer
{
boost::asio::ip::address iHost;
int iPort;
IntroKey iKey;
uint32_t iTag;
};
struct Address
{
TransportStyle transportStyle;
boost::asio::ip::address host;
std::string addressString;
int port, mtu;
uint64_t date;
uint8_t cost;
// SSU only
IntroKey key; // intro key for SSU
std::vector<Introducer> introducers;
bool IsCompatible (const boost::asio::ip::address& other) const
{
return (host.is_v4 () && other.is_v4 ()) ||
(host.is_v6 () && other.is_v6 ());
}
bool operator==(const Address& other) const
{
return transportStyle == other.transportStyle && host == other.host && port == other.port;
}
bool operator!=(const Address& other) const
{
return !(*this == other);
}
};
RouterInfo (const std::string& fullPath);
RouterInfo (): m_Buffer (nullptr) { };
RouterInfo (const RouterInfo& ) = default;
RouterInfo& operator=(const RouterInfo& ) = default;
RouterInfo (const uint8_t * buf, int len);
~RouterInfo ();
std::shared_ptr<const IdentityEx> GetRouterIdentity () const { return m_RouterIdentity; };
void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity);
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
uint64_t GetTimestamp () const { return m_Timestamp; };
std::vector<std::shared_ptr<Address> >& GetAddresses () { return m_Addresses; };
std::shared_ptr<const Address> GetNTCPAddress (bool v4only = true) const;
std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const;
std::shared_ptr<const Address> GetSSUV6Address () const;
void AddNTCPAddress (const char * host, int port);
void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0);
bool AddIntroducer (const Introducer& introducer);
bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only
void DeleteProperty (const std::string& key); // called from RouterContext only
std::string GetProperty (const std::string& key) const; // called from RouterContext only
void ClearProperties () { m_Properties.clear (); };
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
bool IsReachable () const { return m_Caps & Caps::eReachable; };
bool IsNTCP (bool v4only = true) const;
bool IsSSU (bool v4only = true) const;
bool IsV6 () const;
bool IsV4 () const;
void EnableV6 ();
void DisableV6 ();
void EnableV4 ();
void DisableV4 ();
bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; };
bool UsesIntroducer () const;
bool IsIntroducer () const { return m_Caps & eSSUIntroducer; };
bool IsPeerTesting () const { return m_Caps & eSSUTesting; };
bool IsHidden () const { return m_Caps & eHidden; };
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
uint8_t GetCaps () const { return m_Caps; };
void SetCaps (uint8_t caps);
void SetCaps (const char * caps);
void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; };
bool IsUnreachable () const { return m_IsUnreachable; };
const uint8_t * GetBuffer () const { return m_Buffer; };
const uint8_t * LoadBuffer (); // load if necessary
int GetBufferLen () const { return m_BufferLen; };
void CreateBuffer (const PrivateKeys& privateKeys);
bool IsUpdated () const { return m_IsUpdated; };
void SetUpdated (bool updated) { m_IsUpdated = updated; };
bool SaveToFile (const std::string& fullPath);
std::shared_ptr<RouterProfile> GetProfile () const;
void SaveProfile () { if (m_Profile) m_Profile->Save (); };
void Update (const uint8_t * buf, int len);
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
bool IsNewer (const uint8_t * buf, size_t len) const;
// implements RoutingDestination
const IdentHash& GetIdentHash () const { return m_RouterIdentity->GetIdentHash (); };
const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; };
bool IsDestination () const { return false; };
private:
bool LoadFile ();
void ReadFromFile ();
void ReadFromStream (std::istream& s);
void ReadFromBuffer (bool verifySignature);
void WriteToStream (std::ostream& s);
size_t ReadString (char * str, std::istream& s);
void WriteString (const std::string& str, std::ostream& s);
void ExtractCaps (const char * value);
std::shared_ptr<const Address> GetAddress (TransportStyle s, bool v4only, bool v6only = false) const;
void UpdateCapsProperty ();
private:
std::string m_FullPath, m_Family;
std::shared_ptr<const IdentityEx> m_RouterIdentity;
uint8_t * m_Buffer;
size_t m_BufferLen;
uint64_t m_Timestamp;
std::vector<std::shared_ptr<Address> > m_Addresses;
std::map<std::string, std::string> m_Properties;
bool m_IsUpdated, m_IsUnreachable;
uint8_t m_SupportedTransports, m_Caps;
mutable std::shared_ptr<RouterProfile> m_Profile;
};
}
}
#endif

886
SAM.cpp
View File

@@ -1,886 +0,0 @@
#include <string.h>
#include <stdio.h>
#ifdef _MSC_VER
#include <stdlib.h>
#endif
#include <boost/lexical_cast.hpp>
#include "Base.h"
#include "Identity.h"
#include "Log.h"
#include "Destination.h"
#include "ClientContext.h"
#include "SAM.h"
namespace i2p
{
namespace client
{
SAMSocket::SAMSocket (SAMBridge& owner):
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()),
m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false),
m_Stream (nullptr), m_Session (nullptr)
{
}
SAMSocket::~SAMSocket ()
{
Terminate ();
}
void SAMSocket::CloseStream ()
{
if (m_Stream)
{
m_Stream->Close ();
m_Stream.reset ();
}
}
void SAMSocket::Terminate ()
{
CloseStream ();
switch (m_SocketType)
{
case eSAMSocketTypeSession:
m_Owner.CloseSession (m_ID);
break;
case eSAMSocketTypeStream:
{
if (m_Session)
m_Session->DelSocket (shared_from_this ());
break;
}
case eSAMSocketTypeAcceptor:
{
if (m_Session)
{
m_Session->DelSocket (shared_from_this ());
m_Session->localDestination->StopAcceptingStreams ();
}
break;
}
default:
;
}
m_SocketType = eSAMSocketTypeTerminated;
if (m_Socket.is_open()) m_Socket.close ();
m_Session = nullptr;
}
void SAMSocket::ReceiveHandshake ()
{
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Buffer[bytes_transferred] = 0;
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
if (eol)
*eol = 0;
LogPrint (eLogDebug, "SAM: handshake ", m_Buffer);
char * separator = strchr (m_Buffer, ' ');
if (separator)
{
separator = strchr (separator + 1, ' ');
if (separator)
*separator = 0;
}
if (!strcmp (m_Buffer, SAM_HANDSHAKE))
{
std::string version("3.0");
// try to find MIN and MAX, 3.0 if not found
if (separator)
{
separator++;
std::map<std::string, std::string> params;
ExtractParams (separator, params);
auto it = params.find (SAM_PARAM_MAX);
// TODO: check MIN as well
if (it != params.end ())
version = it->second;
}
if (version[0] == '3') // we support v3 (3.0 and 3.1) only
{
#ifdef _MSC_VER
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
#else
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
#endif
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (),
std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
else
SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true);
}
else
{
LogPrint (eLogError, "SAM: handshake mismatch");
Terminate ();
}
}
}
void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
std::bind(&SAMSocket::HandleMessage, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
}
void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close)
{
if (!m_IsSilent)
boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (),
std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, close));
else
{
if (close)
Terminate ();
else
Receive ();
}
}
void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close)
{
if (ecode)
{
LogPrint (eLogError, "SAM: reply send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (close)
Terminate ();
else
Receive ();
}
}
void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "SAM: read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else if (m_SocketType == eSAMSocketTypeStream)
HandleReceived (ecode, bytes_transferred);
else
{
bytes_transferred += m_BufferOffset;
m_BufferOffset = 0;
m_Buffer[bytes_transferred] = 0;
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
if (eol)
{
*eol = 0;
char * separator = strchr (m_Buffer, ' ');
if (separator)
{
separator = strchr (separator + 1, ' ');
if (separator)
*separator = 0;
else
separator = eol;
if (!strcmp (m_Buffer, SAM_SESSION_CREATE))
ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT))
ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT))
ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_DEST_GENERATE))
ProcessDestGenerate ();
else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP))
ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND))
{
size_t len = bytes_transferred - (separator - m_Buffer) - 1;
size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1);
if (processed < len)
{
m_BufferOffset = len - processed;
if (processed > 0)
memmove (m_Buffer, separator + 1 + processed, m_BufferOffset);
else
{
// restore string back
*separator = ' ';
*eol = '\n';
}
}
// since it's SAM v1 reply is not expected
Receive ();
}
else
{
LogPrint (eLogError, "SAM: unexpected message ", m_Buffer);
Terminate ();
}
}
else
{
LogPrint (eLogError, "SAM: malformed message ", m_Buffer);
Terminate ();
}
}
else
{
LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred);
m_BufferOffset = bytes_transferred;
// try to receive remaining message
Receive ();
}
}
}
void SAMSocket::ProcessSessionCreate (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM: session create: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& style = params[SAM_PARAM_STYLE];
std::string& id = params[SAM_PARAM_ID];
std::string& destination = params[SAM_PARAM_DESTINATION];
m_ID = id;
if (m_Owner.FindSession (id))
{
// session exists
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true);
return;
}
// create destination
m_Session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, &params);
if (m_Session)
{
m_SocketType = eSAMSocketTypeSession;
if (style == SAM_VALUE_DATAGRAM)
{
auto dest = m_Session->localDestination->CreateDatagramDestination ();
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
}
if (m_Session->localDestination->IsReady ())
SendSessionCreateReplyOk ();
else
{
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
shared_from_this (), std::placeholders::_1));
}
}
else
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true);
}
void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (m_Session->localDestination->IsReady ())
SendSessionCreateReplyOk ();
else
{
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
shared_from_this (), std::placeholders::_1));
}
}
}
void SAMSocket::SendSessionCreateReplyOk ()
{
uint8_t buf[1024];
char priv[1024];
size_t l = m_Session->localDestination->GetPrivateKeys ().ToBuffer (buf, 1024);
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024);
priv[l1] = 0;
#ifdef _MSC_VER
size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
#else
size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
#endif
SendMessageReply (m_Buffer, l2, false);
}
void SAMSocket::ProcessStreamConnect (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM: stream connect: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& id = params[SAM_PARAM_ID];
std::string& destination = params[SAM_PARAM_DESTINATION];
std::string& silent = params[SAM_PARAM_SILENT];
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
m_ID = id;
m_Session = m_Owner.FindSession (id);
if (m_Session)
{
auto dest = std::make_shared<i2p::data::IdentityEx> ();
size_t len = dest->FromBase64(destination);
if (len > 0)
{
context.GetAddressBook().InsertAddress(dest);
auto leaseSet = m_Session->localDestination->FindLeaseSet(dest->GetIdentHash());
if (leaseSet)
Connect(leaseSet);
else
{
m_Session->localDestination->RequestDestination(dest->GetIdentHash(),
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
shared_from_this(), std::placeholders::_1));
}
}
else
SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true);
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
}
void SAMSocket::Connect (std::shared_ptr<const i2p::data::LeaseSet> remote)
{
m_SocketType = eSAMSocketTypeStream;
m_Session->AddSocket (shared_from_this ());
m_Stream = m_Session->localDestination->CreateStream (remote);
m_Stream->Send ((uint8_t *)m_Buffer, 0); // connect
I2PReceive ();
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
}
void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
if (leaseSet)
Connect (leaseSet);
else
{
LogPrint (eLogError, "SAM: destination to connect not found");
SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true);
}
}
void SAMSocket::ProcessStreamAccept (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM: stream accept: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& id = params[SAM_PARAM_ID];
std::string& silent = params[SAM_PARAM_SILENT];
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
m_ID = id;
m_Session = m_Owner.FindSession (id);
if (m_Session)
{
if (!m_Session->localDestination->IsAcceptingStreams ())
{
m_SocketType = eSAMSocketTypeAcceptor;
m_Session->AddSocket (shared_from_this ());
m_Session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1));
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
}
else
SendMessageReply (SAM_STREAM_STATUS_I2P_ERROR, strlen(SAM_STREAM_STATUS_I2P_ERROR), true);
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
}
size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data)
{
LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
size_t size = boost::lexical_cast<int>(params[SAM_PARAM_SIZE]), offset = data - buf;
if (offset + size <= len)
{
if (m_Session)
{
auto d = m_Session->localDestination->GetDatagramDestination ();
if (d)
{
i2p::data::IdentityEx dest;
dest.FromBase64 (params[SAM_PARAM_DESTINATION]);
d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM: missing datagram destination");
}
else
LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND");
}
else
{
LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset);
return 0; // try to receive more
}
return offset + size;
}
void SAMSocket::ProcessDestGenerate ()
{
LogPrint (eLogDebug, "SAM: dest generate");
auto keys = i2p::data::PrivateKeys::CreateRandomKeys ();
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY,
keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY,
keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ());
#endif
SendMessageReply (m_Buffer, len, false);
}
void SAMSocket::ProcessNamingLookup (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM: naming lookup: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& name = params[SAM_PARAM_NAME];
std::shared_ptr<const i2p::data::IdentityEx> identity;
i2p::data::IdentHash ident;
if (name == "ME")
SendNamingLookupReply (m_Session->localDestination->GetIdentity ());
else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr)
SendNamingLookupReply (identity);
else if (m_Session && m_Session->localDestination &&
context.GetAddressBook ().GetIdentHash (name, ident))
{
auto leaseSet = m_Session->localDestination->FindLeaseSet (ident);
if (leaseSet)
SendNamingLookupReply (leaseSet->GetIdentity ());
else
m_Session->localDestination->RequestDestination (ident,
std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete,
shared_from_this (), std::placeholders::_1, ident));
}
else
{
LogPrint (eLogError, "SAM: naming failed, unknown address ", name);
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
#endif
SendMessageReply (m_Buffer, len, false);
}
}
void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident)
{
if (leaseSet)
{
context.GetAddressBook ().InsertAddress (leaseSet->GetIdentity ());
SendNamingLookupReply (leaseSet->GetIdentity ());
}
else
{
LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found");
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY,
context.GetAddressBook ().ToAddress (ident).c_str());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY,
context.GetAddressBook ().ToAddress (ident).c_str());
#endif
SendMessageReply (m_Buffer, len, false);
}
}
void SAMSocket::SendNamingLookupReply (std::shared_ptr<const i2p::data::IdentityEx> identity)
{
auto base64 = identity->ToBase64 ();
#ifdef _MSC_VER
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
#else
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
#endif
SendMessageReply (m_Buffer, l, false);
}
void SAMSocket::ExtractParams (char * buf, std::map<std::string, std::string>& params)
{
char * separator;
do
{
separator = strchr (buf, ' ');
if (separator) *separator = 0;
char * value = strchr (buf, '=');
if (value)
{
*value = 0;
value++;
params[buf] = value;
}
buf = separator + 1;
}
while (separator);
}
void SAMSocket::Receive ()
{
if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE)
{
LogPrint (eLogError, "SAM: Buffer is full, terminate");
Terminate ();
return;
}
m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset),
std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage,
shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "SAM: read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (m_Stream)
{
auto s = shared_from_this ();
m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred,
[s](const boost::system::error_code& ecode)
{
if (!ecode)
s->Receive ();
else
s->Terminate ();
});
}
}
}
void SAMSocket::I2PReceive ()
{
if (m_Stream)
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE),
std::bind (&SAMSocket::HandleI2PReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2),
SAM_SOCKET_CONNECTION_MAX_IDLE);
}
void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint (eLogError, "SAM: stream read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred),
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
}
}
void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint (eLogError, "SAM: socket write error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
I2PReceive ();
}
void SAMSocket::HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID);
m_Stream = stream;
context.GetAddressBook ().InsertAddress (stream->GetRemoteIdentity ());
auto session = m_Owner.FindSession (m_ID);
if (session)
session->localDestination->StopAcceptingStreams ();
m_SocketType = eSAMSocketTypeStream;
if (!m_IsSilent)
{
// get remote peer address
auto ident_ptr = stream->GetRemoteIdentity();
const size_t ident_len = ident_ptr->GetFullLen();
uint8_t* ident = new uint8_t[ident_len];
// send remote peer address as base64
const size_t l = ident_ptr->ToBuffer (ident, ident_len);
const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE);
delete[] ident;
m_StreamBuffer[l1] = '\n';
HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream
}
else
I2PReceive ();
}
else
LogPrint (eLogWarning, "SAM: I2P acceptor has been reset");
}
void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
LogPrint (eLogDebug, "SAM: datagram received ", len);
auto base64 = from.ToBase64 ();
#ifdef _MSC_VER
size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
#else
size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
#endif
if (len < SAM_SOCKET_BUFFER_SIZE - l)
{
memcpy (m_StreamBuffer + l, buf, len);
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len + l),
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
}
else
LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer");
}
SAMSession::SAMSession (std::shared_ptr<ClientDestination> dest):
localDestination (dest)
{
}
SAMSession::~SAMSession ()
{
CloseStreams();
i2p::client::context.DeleteLocalDestination (localDestination);
}
void SAMSession::CloseStreams ()
{
{
std::lock_guard<std::mutex> lock(m_SocketsMutex);
for (auto sock : m_Sockets) {
sock->CloseStream();
}
}
// XXX: should this be done inside locked parts?
m_Sockets.clear();
}
SAMBridge::SAMBridge (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint)
{
}
SAMBridge::~SAMBridge ()
{
if (m_IsRunning)
Stop ();
}
void SAMBridge::Start ()
{
Accept ();
ReceiveDatagram ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&SAMBridge::Run, this));
}
void SAMBridge::Stop ()
{
m_IsRunning = false;
m_Acceptor.cancel ();
for (auto it: m_Sessions)
it.second->CloseStreams ();
m_Sessions.clear ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void SAMBridge::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SAM: runtime exception: ", ex.what ());
}
}
}
void SAMBridge::Accept ()
{
auto newSocket = std::make_shared<SAMSocket> (*this);
m_Acceptor.async_accept (newSocket->GetSocket (), std::bind (&SAMBridge::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket)
{
if (!ecode)
{
boost::system::error_code ec;
auto ep = socket->GetSocket ().remote_endpoint (ec);
if (!ec)
{
LogPrint (eLogDebug, "SAM: new connection from ", ep);
socket->ReceiveHandshake ();
}
else
LogPrint (eLogError, "SAM: incoming connection error ", ec.message ());
}
else
LogPrint (eLogError, "SAM: accept error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Accept ();
}
std::shared_ptr<SAMSession> SAMBridge::CreateSession (const std::string& id, const std::string& destination,
const std::map<std::string, std::string> * params)
{
std::shared_ptr<ClientDestination> localDestination = nullptr;
if (destination != "")
{
i2p::data::PrivateKeys keys;
keys.FromBase64 (destination);
localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params);
}
else // transient
{
// extract signature type
i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1;
if (params)
{
auto it = params->find (SAM_PARAM_SIGNATURE_TYPE);
if (it != params->end ())
// TODO: extract string values
signatureType = boost::lexical_cast<int> (it->second);
}
localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, params);
}
if (localDestination)
{
auto session = std::make_shared<SAMSession>(localDestination);
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto ret = m_Sessions.insert (std::make_pair(id, session));
if (!ret.second)
LogPrint (eLogWarning, "SAM: Session ", id, " already exists");
return ret.first->second;
}
return nullptr;
}
void SAMBridge::CloseSession (const std::string& id)
{
std::shared_ptr<SAMSession> session;
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto it = m_Sessions.find (id);
if (it != m_Sessions.end ())
{
session = it->second;
m_Sessions.erase (it);
}
}
if (session)
{
session->localDestination->StopAcceptingStreams ();
session->CloseStreams ();
}
}
std::shared_ptr<SAMSession> SAMBridge::FindSession (const std::string& id) const
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto it = m_Sessions.find (id);
if (it != m_Sessions.end ())
return it->second;
return nullptr;
}
void SAMBridge::ReceiveDatagram ()
{
m_DatagramSocket.async_receive_from (
boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE),
m_SenderEndpoint,
std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2));
}
void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (!ecode)
{
m_DatagramReceiveBuffer[bytes_transferred] = 0;
char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n');
*eol = 0; eol++;
size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer);
LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen);
char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' ');
if (sessionID)
{
sessionID++;
char * destination = strchr (sessionID, ' ');
if (destination)
{
*destination = 0; destination++;
auto session = FindSession (sessionID);
if (session)
{
i2p::data::IdentityEx dest;
dest.FromBase64 (destination);
session->localDestination->GetDatagramDestination ()->
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM: Session ", sessionID, " not found");
}
else
LogPrint (eLogError, "SAM: Missing destination key");
}
else
LogPrint (eLogError, "SAM: Missing sessionID");
ReceiveDatagram ();
}
else
LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ());
}
}
}

215
SAM.h
View File

@@ -1,215 +0,0 @@
#ifndef SAM_H__
#define SAM_H__
#include <inttypes.h>
#include <string>
#include <map>
#include <list>
#include <thread>
#include <mutex>
#include <memory>
#include <boost/asio.hpp>
#include "Identity.h"
#include "LeaseSet.h"
#include "Streaming.h"
#include "Destination.h"
namespace i2p
{
namespace client
{
const size_t SAM_SOCKET_BUFFER_SIZE = 8192;
const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds
const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds
const char SAM_HANDSHAKE[] = "HELLO VERSION";
const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n";
const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n";
const char SAM_SESSION_CREATE[] = "SESSION CREATE";
const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n";
const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n";
const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n";
const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n";
const char SAM_STREAM_CONNECT[] = "STREAM CONNECT";
const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n";
const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n";
const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n";
const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n";
const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND";
const char SAM_DEST_GENERATE[] = "DEST GENERATE";
const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n";
const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n";
const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP";
const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n";
const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n";
const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n";
const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n";
const char SAM_PARAM_MIN[] = "MIN";
const char SAM_PARAM_MAX[] = "MAX";
const char SAM_PARAM_STYLE[] = "STYLE";
const char SAM_PARAM_ID[] = "ID";
const char SAM_PARAM_SILENT[] = "SILENT";
const char SAM_PARAM_DESTINATION[] = "DESTINATION";
const char SAM_PARAM_NAME[] = "NAME";
const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE";
const char SAM_PARAM_SIZE[] = "SIZE";
const char SAM_VALUE_TRANSIENT[] = "TRANSIENT";
const char SAM_VALUE_STREAM[] = "STREAM";
const char SAM_VALUE_DATAGRAM[] = "DATAGRAM";
const char SAM_VALUE_RAW[] = "RAW";
const char SAM_VALUE_TRUE[] = "true";
const char SAM_VALUE_FALSE[] = "false";
enum SAMSocketType
{
eSAMSocketTypeUnknown,
eSAMSocketTypeSession,
eSAMSocketTypeStream,
eSAMSocketTypeAcceptor,
eSAMSocketTypeTerminated
};
class SAMBridge;
struct SAMSession;
class SAMSocket: public std::enable_shared_from_this<SAMSocket>
{
public:
SAMSocket (SAMBridge& owner);
~SAMSocket ();
void CloseStream (); // TODO: implement it better
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
void ReceiveHandshake ();
void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; };
SAMSocketType GetSocketType () const { return m_SocketType; };
private:
void Terminate ();
void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void SendMessageReply (const char * msg, size_t len, bool close);
void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close);
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void I2PReceive ();
void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream);
void HandleWriteI2PData (const boost::system::error_code& ecode);
void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void ProcessSessionCreate (char * buf, size_t len);
void ProcessStreamConnect (char * buf, size_t len);
void ProcessStreamAccept (char * buf, size_t len);
void ProcessDestGenerate ();
void ProcessNamingLookup (char * buf, size_t len);
size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0
void ExtractParams (char * buf, std::map<std::string, std::string>& params);
void Connect (std::shared_ptr<const i2p::data::LeaseSet> remote);
void HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet);
void SendNamingLookupReply (std::shared_ptr<const i2p::data::IdentityEx> identity);
void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident);
void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode);
void SendSessionCreateReplyOk ();
private:
SAMBridge& m_Owner;
boost::asio::ip::tcp::socket m_Socket;
boost::asio::deadline_timer m_Timer;
char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1];
size_t m_BufferOffset;
uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE];
SAMSocketType m_SocketType;
std::string m_ID; // nickname
bool m_IsSilent;
std::shared_ptr<i2p::stream::Stream> m_Stream;
std::shared_ptr<SAMSession> m_Session;
};
struct SAMSession
{
std::shared_ptr<ClientDestination> localDestination;
std::list<std::shared_ptr<SAMSocket> > m_Sockets;
std::mutex m_SocketsMutex;
/** safely add a socket to this session */
void AddSocket(std::shared_ptr<SAMSocket> sock) {
std::lock_guard<std::mutex> lock(m_SocketsMutex);
m_Sockets.push_back(sock);
}
/** safely remove a socket from this session */
void DelSocket(std::shared_ptr<SAMSocket> sock) {
std::lock_guard<std::mutex> lock(m_SocketsMutex);
m_Sockets.remove(sock);
}
/** get a list holding a copy of all sam sockets from this session */
std::list<std::shared_ptr<SAMSocket> > ListSockets() {
std::list<std::shared_ptr<SAMSocket> > l;
{
std::lock_guard<std::mutex> lock(m_SocketsMutex);
for( auto & sock : m_Sockets ) l.push_back(sock);
}
return l;
}
SAMSession (std::shared_ptr<ClientDestination> dest);
~SAMSession ();
void CloseStreams ();
};
class SAMBridge
{
public:
SAMBridge (const std::string& address, int port);
~SAMBridge ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<SAMSession> CreateSession (const std::string& id, const std::string& destination, // empty string means transient
const std::map<std::string, std::string> * params);
void CloseSession (const std::string& id);
std::shared_ptr<SAMSession> FindSession (const std::string& id) const;
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket);
void ReceiveDatagram ();
void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint;
boost::asio::ip::udp::socket m_DatagramSocket;
mutable std::mutex m_SessionsMutex;
std::map<std::string, std::shared_ptr<SAMSession> > m_Sessions;
uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1];
public:
// for HTTP
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
};
}
}
#endif

791
SOCKS.cpp
View File

@@ -1,791 +0,0 @@
#include <cstring>
#include <cassert>
#include <string>
#include <atomic>
#include "SOCKS.h"
#include "Identity.h"
#include "Streaming.h"
#include "Destination.h"
#include "ClientContext.h"
#include "I2PEndian.h"
#include "I2PTunnel.h"
#include "I2PService.h"
namespace i2p
{
namespace proxy
{
static const size_t socks_buffer_size = 8192;
static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse
static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192;
static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8;
struct SOCKSDnsAddress
{
uint8_t size;
char value[max_socks_hostname_size];
void FromString (std::string str)
{
size = str.length();
if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size;
memcpy(value,str.c_str(),size);
}
std::string ToString() { return std::string(value, size); }
void push_back (char c) { value[size++] = c; }
};
class SOCKSServer;
class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<SOCKSHandler>
{
private:
enum state
{
GET_SOCKSV,
GET_COMMAND,
GET_PORT,
GET_IPV4,
GET4_IDENT,
GET4A_HOST,
GET5_AUTHNUM,
GET5_AUTH,
GET5_REQUESTV,
GET5_GETRSV,
GET5_GETADDRTYPE,
GET5_IPV6,
GET5_HOST_SIZE,
GET5_HOST,
READY,
UPSTREAM_RESOLVE,
UPSTREAM_CONNECT,
UPSTREAM_HANDSHAKE
};
enum authMethods
{
AUTH_NONE = 0, //No authentication, skip to next step
AUTH_GSSAPI = 1, //GSSAPI authentication
AUTH_USERPASSWD = 2, //Username and password
AUTH_UNACCEPTABLE = 0xff //No acceptable method found
};
enum addrTypes
{
ADDR_IPV4 = 1, //IPv4 address (4 octets)
ADDR_DNS = 3, // DNS name (up to 255 octets)
ADDR_IPV6 = 4 //IPV6 address (16 octets)
};
enum errTypes
{
SOCKS5_OK = 0, // No error for SOCKS5
SOCKS5_GEN_FAIL = 1, // General server failure
SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset
SOCKS5_NET_UNREACH = 3, // Network unreachable
SOCKS5_HOST_UNREACH = 4, // Host unreachable
SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer
SOCKS5_TTL_EXPIRED = 6, // TTL Expired
SOCKS5_CMD_UNSUP = 7, // Command unsuported
SOCKS5_ADDR_UNSUP = 8, // Address type unsuported
SOCKS4_OK = 90, // No error for SOCKS4
SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed
SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server
SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ
};
enum cmdTypes
{
CMD_CONNECT = 1, // TCP Connect
CMD_BIND = 2, // TCP Bind
CMD_UDP = 3 // UDP associate
};
enum socksVersions
{
SOCKS4 = 4, // SOCKS4
SOCKS5 = 5 // SOCKS5
};
union address
{
uint32_t ip;
SOCKSDnsAddress dns;
uint8_t ipv6[16];
};
void EnterState(state nstate, uint8_t parseleft = 1);
bool HandleData(uint8_t *sock_buff, std::size_t len);
bool ValidateSOCKSRequest();
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void Terminate();
void AsyncSockRead();
boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method);
boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port);
boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port);
boost::asio::const_buffers_1 GenerateUpstreamRequest();
bool Socks5ChooseAuth();
void SocksRequestFailed(errTypes error);
void SocksRequestSuccess();
void SentSocksFailed(const boost::system::error_code & ecode);
void SentSocksDone(const boost::system::error_code & ecode);
void SentSocksResponse(const boost::system::error_code & ecode);
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
void ForwardSOCKS();
void SocksUpstreamSuccess();
void AsyncUpstreamSockRead();
void SendUpstreamRequest();
void HandleUpstreamData(uint8_t * buff, std::size_t len);
void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void HandleUpstreamConnected(const boost::system::error_code & ecode,
boost::asio::ip::tcp::resolver::iterator itr);
void HandleUpstreamResolved(const boost::system::error_code & ecode,
boost::asio::ip::tcp::resolver::iterator itr);
boost::asio::ip::tcp::resolver m_proxy_resolver;
uint8_t m_sock_buff[socks_buffer_size];
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock, m_upstreamSock;
std::shared_ptr<i2p::stream::Stream> m_stream;
uint8_t *m_remaining_data; //Data left to be sent
uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded
uint8_t m_response[7+max_socks_hostname_size];
uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE];
uint8_t m_upstream_request[14+max_socks_hostname_size];
std::size_t m_upstream_response_len;
address m_address; //Address
std::size_t m_remaining_data_len; //Size of the data left to be sent
uint32_t m_4aip; //Used in 4a requests
uint16_t m_port;
uint8_t m_command;
uint8_t m_parseleft; //Octets left to parse
authMethods m_authchosen; //Authentication chosen
addrTypes m_addrtype; //Address type chosen
socksVersions m_socksv; //Socks version
cmdTypes m_cmd; // Command requested
state m_state;
const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses?
const std::string m_UpstreamProxyAddress;
const uint16_t m_UpstreamProxyPort;
public:
SOCKSHandler(SOCKSServer * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) :
I2PServiceHandler(parent),
m_proxy_resolver(parent->GetService()),
m_sock(sock), m_stream(nullptr),
m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4),
m_UseUpstreamProxy(useUpstream),
m_UpstreamProxyAddress(upstreamAddr),
m_UpstreamProxyPort(upstreamPort)
{ m_address.ip = 0; EnterState(GET_SOCKSV); }
~SOCKSHandler() { Terminate(); }
void Handle() { AsyncSockRead(); }
};
void SOCKSHandler::AsyncSockRead()
{
LogPrint(eLogDebug, "SOCKS: async sock read");
if (m_sock) {
m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size),
std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError,"SOCKS: no socket for read");
}
}
void SOCKSHandler::Terminate()
{
if (Kill()) return;
if (m_sock)
{
LogPrint(eLogDebug, "SOCKS: closing socket");
m_sock->close();
m_sock = nullptr;
}
if (m_upstreamSock)
{
LogPrint(eLogDebug, "SOCKS: closing upstream socket");
m_upstreamSock->close();
m_upstreamSock = nullptr;
}
if (m_stream)
{
LogPrint(eLogDebug, "SOCKS: closing stream");
m_stream.reset ();
}
Done(shared_from_this());
}
boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port)
{
assert(error >= SOCKS4_OK);
m_response[0] = '\x00'; //Version
m_response[1] = error; //Response code
htobe16buf(m_response+2,port); //Port
htobe32buf(m_response+4,ip); //IP
return boost::asio::const_buffers_1(m_response,8);
}
boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port)
{
size_t size = 6;
assert(error <= SOCKS5_ADDR_UNSUP);
m_response[0] = '\x05'; //Version
m_response[1] = error; //Response code
m_response[2] = '\x00'; //RSV
m_response[3] = type; //Address type
switch (type)
{
case ADDR_IPV4:
size = 10;
htobe32buf(m_response+4,addr.ip);
break;
case ADDR_IPV6:
size = 22;
memcpy(m_response+4,addr.ipv6, 16);
break;
case ADDR_DNS:
size = 7+addr.dns.size;
m_response[4] = addr.dns.size;
memcpy(m_response+5,addr.dns.value, addr.dns.size);
break;
}
htobe16buf(m_response+size-2,port); //Port
return boost::asio::const_buffers_1(m_response,size);
}
boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest()
{
size_t upstreamRequestSize = 0;
// TODO: negotiate with upstream
// SOCKS 4a
m_upstream_request[0] = '\x04'; //version
m_upstream_request[1] = m_cmd;
htobe16buf(m_upstream_request+2, m_port);
m_upstream_request[4] = 0;
m_upstream_request[5] = 0;
m_upstream_request[6] = 0;
m_upstream_request[7] = 1;
// user id
m_upstream_request[8] = 'i';
m_upstream_request[9] = '2';
m_upstream_request[10] = 'p';
m_upstream_request[11] = 'd';
m_upstream_request[12] = 0;
upstreamRequestSize += 13;
if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) {
// bounds check okay
memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size);
upstreamRequestSize += m_address.dns.size;
// null terminate
m_upstream_request[++upstreamRequestSize] = 0;
} else {
LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )");
}
return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize);
}
bool SOCKSHandler::Socks5ChooseAuth()
{
m_response[0] = '\x05'; //Version
m_response[1] = m_authchosen; //Response code
boost::asio::const_buffers_1 response(m_response,2);
if (m_authchosen == AUTH_UNACCEPTABLE)
{
LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed");
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
shared_from_this(), std::placeholders::_1));
return false;
}
else
{
LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen);
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse,
shared_from_this(), std::placeholders::_1));
return true;
}
}
/* All hope is lost beyond this point */
void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error)
{
boost::asio::const_buffers_1 response(nullptr,0);
assert(error != SOCKS4_OK && error != SOCKS5_OK);
switch (m_socksv)
{
case SOCKS4:
LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error);
if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors
response = GenerateSOCKS4Response(error, m_4aip, m_port);
break;
case SOCKS5:
LogPrint(eLogWarning, "SOCKS: v5 request failed: ", error);
response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port);
break;
}
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
shared_from_this(), std::placeholders::_1));
}
void SOCKSHandler::SocksRequestSuccess()
{
boost::asio::const_buffers_1 response(nullptr,0);
//TODO: this should depend on things like the command type and callbacks may change
switch (m_socksv)
{
case SOCKS4:
LogPrint(eLogInfo, "SOCKS: v4 connection success");
response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port);
break;
case SOCKS5:
LogPrint(eLogInfo, "SOCKS: v5 connection success");
auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash());
address ad; ad.dns.FromString(s);
//HACK only 16 bits passed in port as SOCKS5 doesn't allow for more
response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID());
break;
}
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone,
shared_from_this(), std::placeholders::_1));
}
void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) {
switch (nstate)
{
case GET_PORT: parseleft = 2; break;
case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break;
case GET4_IDENT: m_4aip = m_address.ip; break;
case GET4A_HOST:
case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break;
case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break;
default:;
}
m_parseleft = parseleft;
m_state = nstate;
}
bool SOCKSHandler::ValidateSOCKSRequest()
{
if ( m_cmd != CMD_CONNECT )
{
//TODO: we need to support binds and other shit!
LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd);
SocksRequestFailed(SOCKS5_CMD_UNSUP);
return false;
}
//TODO: we may want to support other address types!
if ( m_addrtype != ADDR_DNS )
{
switch (m_socksv)
{
case SOCKS5:
LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype);
break;
case SOCKS4:
LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4");
break;
}
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
return false;
}
return true;
}
bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len)
{
assert(len); // This should always be called with a least a byte left to parse
while (len > 0)
{
switch (m_state)
{
case GET_SOCKSV:
m_socksv = (SOCKSHandler::socksVersions) *sock_buff;
switch (*sock_buff)
{
case SOCKS4:
EnterState(GET_COMMAND); //Initialize the parser at the right position
break;
case SOCKS5:
EnterState(GET5_AUTHNUM); //Initialize the parser at the right position
break;
default:
LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff));
Terminate();
return false;
}
break;
case GET5_AUTHNUM:
EnterState(GET5_AUTH, *sock_buff);
break;
case GET5_AUTH:
m_parseleft --;
if (*sock_buff == AUTH_NONE)
m_authchosen = AUTH_NONE;
if ( m_parseleft == 0 )
{
if (!Socks5ChooseAuth()) return false;
EnterState(GET5_REQUESTV);
}
break;
case GET_COMMAND:
switch (*sock_buff)
{
case CMD_CONNECT:
case CMD_BIND:
break;
case CMD_UDP:
if (m_socksv == SOCKS5) break;
default:
LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
m_cmd = (SOCKSHandler::cmdTypes)*sock_buff;
switch (m_socksv)
{
case SOCKS5: EnterState(GET5_GETRSV); break;
case SOCKS4: EnterState(GET_PORT); break;
}
break;
case GET_PORT:
m_port = (m_port << 8)|((uint16_t)*sock_buff);
m_parseleft--;
if (m_parseleft == 0)
{
switch (m_socksv)
{
case SOCKS5: EnterState(READY); break;
case SOCKS4: EnterState(GET_IPV4); break;
}
}
break;
case GET_IPV4:
m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff);
m_parseleft--;
if (m_parseleft == 0)
{
switch (m_socksv)
{
case SOCKS5: EnterState(GET_PORT); break;
case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break;
}
}
break;
case GET4_IDENT:
if (!*sock_buff)
{
if( m_4aip == 0 || m_4aip > 255 )
EnterState(READY);
else
EnterState(GET4A_HOST);
}
break;
case GET4A_HOST:
if (!*sock_buff)
{
EnterState(READY);
break;
}
if (m_address.dns.size >= max_socks_hostname_size)
{
LogPrint(eLogError, "SOCKS: v4a req failed: destination is too large");
SocksRequestFailed(SOCKS4_FAIL);
return false;
}
m_address.dns.push_back(*sock_buff);
break;
case GET5_REQUESTV:
if (*sock_buff != SOCKS5)
{
LogPrint(eLogError,"SOCKS: v5 rejected unknown request version: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
EnterState(GET_COMMAND);
break;
case GET5_GETRSV:
if ( *sock_buff != 0 )
{
LogPrint(eLogError, "SOCKS: v5 unknown reserved field: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
EnterState(GET5_GETADDRTYPE);
break;
case GET5_GETADDRTYPE:
switch (*sock_buff)
{
case ADDR_IPV4: EnterState(GET_IPV4); break;
case ADDR_IPV6: EnterState(GET5_IPV6); break;
case ADDR_DNS : EnterState(GET5_HOST_SIZE); break;
default:
LogPrint(eLogError, "SOCKS: v5 unknown address type: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
break;
case GET5_IPV6:
m_address.ipv6[16-m_parseleft] = *sock_buff;
m_parseleft--;
if (m_parseleft == 0) EnterState(GET_PORT);
break;
case GET5_HOST_SIZE:
EnterState(GET5_HOST, *sock_buff);
break;
case GET5_HOST:
m_address.dns.push_back(*sock_buff);
m_parseleft--;
if (m_parseleft == 0) EnterState(GET_PORT);
break;
default:
LogPrint(eLogError, "SOCKS: parse state?? ", m_state);
Terminate();
return false;
}
sock_buff++;
len--;
if (m_state == READY)
{
m_remaining_data_len = len;
m_remaining_data = sock_buff;
return ValidateSOCKSRequest();
}
}
return true;
}
void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
{
LogPrint(eLogDebug, "SOCKS: recieved ", len, " bytes");
if(ecode)
{
LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode);
Terminate();
return;
}
if (HandleData(m_sock_buff, len))
{
if (m_state == READY)
{
const std::string addr = m_address.dns.ToString();
LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port);
const size_t addrlen = addr.size();
// does it end with .i2p?
if ( addr.rfind(".i2p") == addrlen - 4) {
// yes it does, make an i2p session
GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete,
shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port);
} else if (m_UseUpstreamProxy) {
// forward it to upstream proxy
ForwardSOCKS();
} else {
// no upstream proxy
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
}
}
else
AsyncSockRead();
}
}
void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode)
{
if (ecode)
LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ());
Terminate();
}
void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode)
{
if (!ecode)
{
if (Kill()) return;
LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection");
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, m_stream);
GetOwner()->AddHandler (connection);
connection->I2PConnect (m_remaining_data,m_remaining_data_len);
Done(shared_from_this());
}
else
{
LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ());
Terminate();
}
}
void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode)
{
if (ecode)
{
LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ());
Terminate();
}
}
void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
m_stream = stream;
SocksRequestSuccess();
}
else
{
LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info");
SocksRequestFailed(SOCKS5_HOST_UNREACH);
}
}
void SOCKSHandler::ForwardSOCKS()
{
LogPrint(eLogInfo, "SOCKS: forwarding to upstream");
EnterState(UPSTREAM_RESOLVE);
boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress,boost::lexical_cast<std::string>(m_UpstreamProxyPort) );
m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
}
void SOCKSHandler::AsyncUpstreamSockRead()
{
LogPrint(eLogDebug, "SOCKS: async upstream sock read");
if (m_upstreamSock) {
m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE),
std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError, "SOCKS: no upstream socket for read");
SocksRequestFailed(SOCKS5_GEN_FAIL);
}
}
void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered)
{
if (ecode) {
if (m_state == UPSTREAM_HANDSHAKE ) {
// we are trying to handshake but it failed
SocksRequestFailed(SOCKS5_NET_UNREACH);
} else {
LogPrint(eLogError, "SOCKS: bad state when reading from upstream: ", (int) m_state);
}
return;
}
HandleUpstreamData(m_upstream_response, bytes_transfered);
}
void SOCKSHandler::SocksUpstreamSuccess()
{
LogPrint(eLogInfo, "SOCKS: upstream success");
boost::asio::const_buffers_1 response(nullptr, 0);
switch (m_socksv)
{
case SOCKS4:
LogPrint(eLogInfo, "SOCKS: v4 connection success");
response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port);
break;
case SOCKS5:
LogPrint(eLogInfo, "SOCKS: v5 connection success");
//HACK only 16 bits passed in port as SOCKS5 doesn't allow for more
response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, m_address, m_port);
break;
}
m_sock->send(response);
auto forwarder = std::make_shared<i2p::client::TCPIPPipe>(GetOwner(), m_sock, m_upstreamSock);
m_upstreamSock = nullptr;
m_sock = nullptr;
GetOwner()->AddHandler(forwarder);
forwarder->Start();
Terminate();
}
void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len)
{
if (m_state == UPSTREAM_HANDSHAKE) {
m_upstream_response_len += len;
// handle handshake data
if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
// too small, continue reading
AsyncUpstreamSockRead();
} else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
// just right
uint8_t resp = m_upstream_response[1];
if (resp == SOCKS4_OK) {
// we have connected !
SocksUpstreamSuccess();
} else {
// upstream failure
LogPrint(eLogError, "SOCKS: upstream proxy failure: ", (int) resp);
// TODO: runtime error?
SocksRequestFailed(SOCKS5_GEN_FAIL);
}
} else {
// too big
SocksRequestFailed(SOCKS5_GEN_FAIL);
}
} else {
// invalid state
LogPrint(eLogError, "SOCKS: invalid state reading from upstream: ", (int) m_state);
}
}
void SOCKSHandler::SendUpstreamRequest()
{
LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy");
EnterState(UPSTREAM_HANDSHAKE);
if (m_upstreamSock) {
boost::asio::write(*m_upstreamSock,
GenerateUpstreamRequest());
AsyncUpstreamSockRead();
} else {
LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to");
}
}
void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
{
if (ecode) {
LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message());
SocksRequestFailed(SOCKS5_NET_UNREACH);
return;
}
LogPrint(eLogInfo, "SOCKS: connected to upstream proxy");
SendUpstreamRequest();
}
void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
{
if (ecode) {
// error resolving
LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message());
SocksRequestFailed(SOCKS5_NET_UNREACH);
return;
}
LogPrint(eLogInfo, "SOCKS: upstream proxy resolved");
EnterState(UPSTREAM_CONNECT);
auto & service = GetOwner()->GetService();
m_upstreamSock = std::make_shared<boost::asio::ip::tcp::socket>(service);
boost::asio::async_connect(*m_upstreamSock, itr,
std::bind(&SOCKSHandler::HandleUpstreamConnected,
shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
SOCKSServer::SOCKSServer(const std::string& address, int port, const std::string& outAddress, uint16_t outPort,
std::shared_ptr<i2p::client::ClientDestination> localDestination) :
TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ())
{
m_UseUpstreamProxy = false;
if (outAddress.length() > 0)
SetUpstreamProxy(outAddress, outPort);
}
std::shared_ptr<i2p::client::I2PServiceHandler> SOCKSServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
return std::make_shared<SOCKSHandler> (this, socket, m_UpstreamProxyAddress, m_UpstreamProxyPort, m_UseUpstreamProxy);
}
void SOCKSServer::SetUpstreamProxy(const std::string & addr, const uint16_t port)
{
m_UpstreamProxyAddress = addr;
m_UpstreamProxyPort = port;
m_UseUpstreamProxy = true;
}
}
}

40
SOCKS.h
View File

@@ -1,40 +0,0 @@
#ifndef SOCKS_H__
#define SOCKS_H__
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <mutex>
#include "I2PService.h"
namespace i2p
{
namespace proxy
{
class SOCKSServer: public i2p::client::TCPIPAcceptor
{
public:
SOCKSServer(const std::string& address, int port, const std::string& outAddress, uint16_t outPort,
std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
~SOCKSServer() {};
void SetUpstreamProxy(const std::string & addr, const uint16_t port);
protected:
// Implements TCPIPAcceptor
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
const char* GetName() { return "SOCKS"; }
private:
std::string m_UpstreamProxyAddress;
uint16_t m_UpstreamProxyPort;
bool m_UseUpstreamProxy;
};
typedef SOCKSServer SOCKSProxy;
}
}
#endif

604
SSU.cpp
View File

@@ -1,604 +0,0 @@
#include <string.h>
#include <boost/bind.hpp>
#include "Log.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.h"
#include "SSU.h"
namespace i2p
{
namespace transport
{
SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService),
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService),
m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service)
{
m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535));
m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535));
if (context.SupportsV6 ())
{
m_SocketV6.open (boost::asio::ip::udp::v6());
m_SocketV6.set_option (boost::asio::ip::v6_only (true));
m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (65535));
m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (65535));
m_SocketV6.bind (m_EndpointV6);
}
}
SSUServer::~SSUServer ()
{
}
void SSUServer::Start ()
{
m_IsRunning = true;
m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
if (context.SupportsV6 ())
{
m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this));
m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this));
}
SchedulePeerTestsCleanupTimer ();
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
}
void SSUServer::Stop ()
{
DeleteAllSessions ();
m_IsRunning = false;
m_Service.stop ();
m_Socket.close ();
m_ServiceV6.stop ();
m_SocketV6.close ();
m_ReceiversService.stop ();
if (m_ReceiversThread)
{
m_ReceiversThread->join ();
delete m_ReceiversThread;
m_ReceiversThread = nullptr;
}
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
if (m_ThreadV6)
{
m_ThreadV6->join ();
delete m_ThreadV6;
m_ThreadV6 = nullptr;
}
}
void SSUServer::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ());
}
}
}
void SSUServer::RunV6 ()
{
while (m_IsRunning)
{
try
{
m_ServiceV6.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ());
}
}
}
void SSUServer::RunReceivers ()
{
while (m_IsRunning)
{
try
{
m_ReceiversService.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ());
}
}
}
void SSUServer::AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay)
{
m_Relays[tag] = relay;
}
std::shared_ptr<SSUSession> SSUServer::FindRelaySession (uint32_t tag)
{
auto it = m_Relays.find (tag);
if (it != m_Relays.end ())
return FindSession (it->second);
return nullptr;
}
void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to)
{
if (to.protocol () == boost::asio::ip::udp::v4())
m_Socket.send_to (boost::asio::buffer (buf, len), to);
else
m_SocketV6.send_to (boost::asio::buffer (buf, len), to);
}
void SSUServer::Receive ()
{
SSUPacket * packet = new SSUPacket ();
m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from,
std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet));
}
void SSUServer::ReceiveV6 ()
{
SSUPacket * packet = new SSUPacket ();
m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from,
std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet));
}
void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
{
if (!ecode)
{
packet->len = bytes_transferred;
std::vector<SSUPacket *> packets;
packets.push_back (packet);
boost::system::error_code ec;
size_t moreBytes = m_Socket.available(ec);
while (moreBytes && packets.size () < 25)
{
packet = new SSUPacket ();
packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from);
packets.push_back (packet);
moreBytes = m_Socket.available();
}
m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions));
Receive ();
}
else
{
LogPrint (eLogError, "SSU: receive error: ", ecode.message ());
delete packet;
}
}
void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
{
if (!ecode)
{
packet->len = bytes_transferred;
std::vector<SSUPacket *> packets;
packets.push_back (packet);
size_t moreBytes = m_SocketV6.available ();
while (moreBytes && packets.size () < 25)
{
packet = new SSUPacket ();
packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from);
packets.push_back (packet);
moreBytes = m_SocketV6.available();
}
m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6));
ReceiveV6 ();
}
else
{
LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ());
delete packet;
}
}
void SSUServer::HandleReceivedPackets (std::vector<SSUPacket *> packets,
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > * sessions)
{
std::shared_ptr<SSUSession> session;
for (auto it1: packets)
{
auto packet = it1;
try
{
if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous
{
if (session) session->FlushData ();
auto it = sessions->find (packet->from);
if (it != sessions->end ())
session = it->second;
if (!session)
{
session = std::make_shared<SSUSession> (*this, packet->from);
session->WaitForConnect ();
(*sessions)[packet->from] = session;
LogPrint (eLogInfo, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created");
}
}
session->ProcessNextMessage (packet->buf, packet->len, packet->from);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ());
if (session) session->FlushData ();
session = nullptr;
}
delete packet;
}
if (session) session->FlushData ();
}
std::shared_ptr<SSUSession> SSUServer::FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const
{
if (!router) return nullptr;
auto address = router->GetSSUAddress (true); // v4 only
if (!address) return nullptr;
auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
if (session || !context.SupportsV6 ())
return session;
// try v6
address = router->GetSSUV6Address ();
if (!address) return nullptr;
return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
}
std::shared_ptr<SSUSession> SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const
{
auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
auto it = sessions.find (e);
if (it != sessions.end ())
return it->second;
else
return nullptr;
}
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest)
{
auto address = router->GetSSUAddress (!context.SupportsV6 ());
if (address)
CreateSession (router, address->host, address->port, peerTest);
else
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
}
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
const boost::asio::ip::address& addr, int port, bool peerTest)
{
if (router)
{
if (router->UsesIntroducer ())
m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread
else
{
boost::asio::ip::udp::endpoint remoteEndpoint (addr, port);
auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service;
s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest));
}
}
}
void SSUServer::CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest)
{
auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
auto it = sessions.find (remoteEndpoint);
if (it != sessions.end ())
{
auto session = it->second;
if (peerTest && session->GetState () == eSessionStateEstablished)
session->SendPeerTest ();
}
else
{
// otherwise create new session
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
sessions[remoteEndpoint] = session;
// connect
LogPrint (eLogInfo, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ",
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
session->Connect ();
}
}
void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest)
{
if (router && router->UsesIntroducer ())
{
auto address = router->GetSSUAddress (true); // v4 only for now
if (address)
{
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
auto it = m_Sessions.find (remoteEndpoint);
// check if session if presented alredy
if (it != m_Sessions.end ())
{
auto session = it->second;
if (peerTest && session->GetState () == eSessionStateEstablished)
session->SendPeerTest ();
return;
}
// create new session
int numIntroducers = address->introducers.size ();
if (numIntroducers > 0)
{
std::shared_ptr<SSUSession> introducerSession;
const i2p::data::RouterInfo::Introducer * introducer = nullptr;
// we might have a session to introducer already
for (int i = 0; i < numIntroducers; i++)
{
auto intr = &(address->introducers[i]);
boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort);
if (ep.address ().is_v4 ()) // ipv4 only
{
if (!introducer) introducer = intr; // we pick first one for now
it = m_Sessions.find (ep);
if (it != m_Sessions.end ())
{
introducerSession = it->second;
break;
}
}
}
if (!introducer)
{
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 introducers present");
return;
}
if (introducerSession) // session found
LogPrint (eLogInfo, "SSU: Session to introducer already exists");
else // create new
{
LogPrint (eLogInfo, "SSU: Creating new session to introducer");
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router);
m_Sessions[introducerEndpoint] = introducerSession;
}
// create session
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
m_Sessions[remoteEndpoint] = session;
// introduce
LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()),
"] through introducer ", introducer->iHost, ":", introducer->iPort);
session->WaitForIntroduction ();
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable
{
uint8_t buf[1];
Send (buf, 0, remoteEndpoint); // send HolePunch
}
introducerSession->Introduce (*introducer, router);
}
else
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present");
}
else
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
}
}
void SSUServer::DeleteSession (std::shared_ptr<SSUSession> session)
{
if (session)
{
session->Close ();
auto& ep = session->GetRemoteEndpoint ();
if (ep.address ().is_v6 ())
m_SessionsV6.erase (ep);
else
m_Sessions.erase (ep);
}
}
void SSUServer::DeleteAllSessions ()
{
for (auto it: m_Sessions)
it.second->Close ();
m_Sessions.clear ();
for (auto it: m_SessionsV6)
it.second->Close ();
m_SessionsV6.clear ();
}
template<typename Filter>
std::shared_ptr<SSUSession> SSUServer::GetRandomV4Session (Filter filter) // v4 only
{
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
for (auto s :m_Sessions)
if (filter (s.second)) filteredSessions.push_back (s.second);
if (filteredSessions.size () > 0)
{
auto ind = rand () % filteredSessions.size ();
return filteredSessions[ind];
}
return nullptr;
}
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded) // v4 only
{
return GetRandomV4Session (
[excluded](std::shared_ptr<SSUSession> session)->bool
{
return session->GetState () == eSessionStateEstablished && !session->IsV6 () &&
session != excluded;
}
);
}
std::set<SSUSession *> SSUServer::FindIntroducers (int maxNumIntroducers)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::set<SSUSession *> ret;
for (int i = 0; i < maxNumIntroducers; i++)
{
auto session = GetRandomV4Session (
[&ret, ts](std::shared_ptr<SSUSession> session)->bool
{
return session->GetRelayTag () && !ret.count (session.get ()) &&
session->GetState () == eSessionStateEstablished &&
ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION;
}
);
if (session)
{
ret.insert (session.get ());
break;
}
}
return ret;
}
void SSUServer::ScheduleIntroducersUpdateTimer ()
{
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1));
}
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
// timeout expired
if (i2p::context.GetStatus () == eRouterStatusTesting)
{
// we still don't know if we need introducers
ScheduleIntroducersUpdateTimer ();
return;
}
if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore
// we are firewalled
if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable ();
std::list<boost::asio::ip::udp::endpoint> newList;
size_t numIntroducers = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it :m_Introducers)
{
auto session = FindSession (it);
if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION)
{
session->SendKeepAlive ();
newList.push_back (it);
numIntroducers++;
}
else
i2p::context.RemoveIntroducer (it);
}
if (numIntroducers < SSU_MAX_NUM_INTRODUCERS)
{
// create new
auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS);
if (introducers.size () > 0)
{
for (auto it1: introducers)
{
auto& ep = it1->GetRemoteEndpoint ();
i2p::data::RouterInfo::Introducer introducer;
introducer.iHost = ep.address ();
introducer.iPort = ep.port ();
introducer.iTag = it1->GetRelayTag ();
introducer.iKey = it1->GetIntroKey ();
if (i2p::context.AddIntroducer (introducer))
{
newList.push_back (ep);
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
}
}
}
}
m_Introducers = newList;
if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS)
{
auto introducer = i2p::data::netdb.GetRandomIntroducer ();
if (introducer)
CreateSession (introducer);
}
ScheduleIntroducersUpdateTimer ();
}
}
void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session)
{
m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session };
}
PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
return it->second.role;
else
return ePeerTestParticipantUnknown;
}
std::shared_ptr<SSUSession> SSUServer::GetPeerTestSession (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
return it->second.session;
else
return nullptr;
}
void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
it->second.role = role;
}
void SSUServer::RemovePeerTest (uint32_t nonce)
{
m_PeerTests.erase (nonce);
}
void SSUServer::SchedulePeerTestsCleanupTimer ()
{
m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT));
m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer,
this, std::placeholders::_1));
}
void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
int numDeleted = 0;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
{
if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL)
{
numDeleted++;
it = m_PeerTests.erase (it);
}
else
it++;
}
if (numDeleted > 0)
LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired");
SchedulePeerTestsCleanupTimer ();
}
}
}
}

119
SSU.h
View File

@@ -1,119 +0,0 @@
#ifndef SSU_H__
#define SSU_H__
#include <inttypes.h>
#include <string.h>
#include <map>
#include <list>
#include <set>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>
#include "Crypto.h"
#include "I2PEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "SSUSession.h"
namespace i2p
{
namespace transport
{
const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds
const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds
const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
const size_t SSU_MAX_NUM_INTRODUCERS = 3;
struct SSUPacket
{
i2p::crypto::AESAlignedBuffer<1500> buf;
boost::asio::ip::udp::endpoint from;
size_t len;
};
class SSUServer
{
public:
SSUServer (int port);
~SSUServer ();
void Start ();
void Stop ();
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false);
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
const boost::asio::ip::address& addr, int port, bool peerTest = false);
void CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest);
std::shared_ptr<SSUSession> FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const;
std::shared_ptr<SSUSession> FindSession (const boost::asio::ip::udp::endpoint& e) const;
std::shared_ptr<SSUSession> GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded);
void DeleteSession (std::shared_ptr<SSUSession> session);
void DeleteAllSessions ();
boost::asio::io_service& GetService () { return m_Service; };
boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; };
const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; };
void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to);
void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay);
std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag);
void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr);
PeerTestParticipant GetPeerTestParticipant (uint32_t nonce);
std::shared_ptr<SSUSession> GetPeerTestSession (uint32_t nonce);
void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role);
void RemovePeerTest (uint32_t nonce);
private:
void Run ();
void RunV6 ();
void RunReceivers ();
void Receive ();
void ReceiveV6 ();
void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
void HandleReceivedPackets (std::vector<SSUPacket *> packets,
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> >* sessions);
void CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false);
template<typename Filter>
std::shared_ptr<SSUSession> GetRandomV4Session (Filter filter);
std::set<SSUSession *> FindIntroducers (int maxNumIntroducers);
void ScheduleIntroducersUpdateTimer ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode);
void SchedulePeerTestsCleanupTimer ();
void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode);
private:
struct PeerTest
{
uint64_t creationTime;
PeerTestParticipant role;
std::shared_ptr<SSUSession> session; // for Bob to Alice
};
bool m_IsRunning;
std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread;
boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService;
boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork;
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
boost::asio::ip::udp::socket m_Socket, m_SocketV6;
boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer;
std::list<boost::asio::ip::udp::endpoint> m_Introducers; // introducers we are connected to
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions, m_SessionsV6;
std::map<uint32_t, boost::asio::ip::udp::endpoint> m_Relays; // we are introducer
std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds
public:
// for HTTP only
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; };
};
}
}
#endif

View File

@@ -1,508 +0,0 @@
#include <stdlib.h>
#include <boost/bind.hpp>
#include "Log.h"
#include "Timestamp.h"
#include "NetDb.h"
#include "SSU.h"
#include "SSUData.h"
namespace i2p
{
namespace transport
{
void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize)
{
if (msg->len + fragmentSize > msg->maxLen)
{
LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough");
auto newMsg = NewI2NPMessage ();
*newMsg = *msg;
msg = newMsg;
}
if (msg->Concat (fragment, fragmentSize) < fragmentSize)
LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen);
nextFragmentNum++;
}
SSUData::SSUData (SSUSession& session):
m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()),
m_IncompleteMessagesCleanupTimer (session.GetService ()),
m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE),
m_PacketSize (m_MaxPacketSize)
{
}
SSUData::~SSUData ()
{
}
void SSUData::Start ()
{
ScheduleIncompleteMessagesCleanup ();
}
void SSUData::Stop ()
{
m_ResendTimer.cancel ();
m_DecayTimer.cancel ();
m_IncompleteMessagesCleanupTimer.cancel ();
}
void SSUData::AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter)
{
if (remoteRouter) return;
auto ssuAddress = remoteRouter->GetSSUAddress ();
if (ssuAddress && ssuAddress->mtu)
{
if (m_Session.IsV6 ())
m_PacketSize = ssuAddress->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE;
else
m_PacketSize = ssuAddress->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE;
if (m_PacketSize > 0)
{
// make sure packet size multiple of 16
m_PacketSize >>= 4;
m_PacketSize <<= 4;
if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize;
LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->mtu, " packet size=", m_PacketSize);
}
else
{
LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->mtu);
m_PacketSize = m_MaxPacketSize;
}
}
}
void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent)
{
auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent);
if (routerInfo)
AdjustPacketSize (routerInfo);
}
void SSUData::ProcessSentMessageAck (uint32_t msgID)
{
auto it = m_SentMessages.find (msgID);
if (it != m_SentMessages.end ())
{
m_SentMessages.erase (it);
if (m_SentMessages.empty ())
m_ResendTimer.cancel ();
}
}
void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag)
{
if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED)
{
// explicit ACKs
uint8_t numAcks =*buf;
buf++;
for (int i = 0; i < numAcks; i++)
ProcessSentMessageAck (bufbe32toh (buf+i*4));
buf += numAcks*4;
}
if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED)
{
// explicit ACK bitfields
uint8_t numBitfields =*buf;
buf++;
for (int i = 0; i < numBitfields; i++)
{
uint32_t msgID = bufbe32toh (buf);
buf += 4; // msgID
auto it = m_SentMessages.find (msgID);
// process individual Ack bitfields
bool isNonLast = false;
int fragment = 0;
do
{
uint8_t bitfield = *buf;
isNonLast = bitfield & 0x80;
bitfield &= 0x7F; // clear MSB
if (bitfield && it != m_SentMessages.end ())
{
int numSentFragments = it->second->fragments.size ();
// process bits
uint8_t mask = 0x01;
for (int j = 0; j < 7; j++)
{
if (bitfield & mask)
{
if (fragment < numSentFragments)
it->second->fragments[fragment].reset (nullptr);
}
fragment++;
mask <<= 1;
}
}
buf++;
}
while (isNonLast);
}
}
}
void SSUData::ProcessFragments (uint8_t * buf)
{
uint8_t numFragments = *buf; // number of fragments
buf++;
for (int i = 0; i < numFragments; i++)
{
uint32_t msgID = bufbe32toh (buf); // message ID
buf += 4;
uint8_t frag[4];
frag[0] = 0;
memcpy (frag + 1, buf, 3);
buf += 3;
uint32_t fragmentInfo = bufbe32toh (frag); // fragment info
uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13
bool isLast = fragmentInfo & 0x010000; // bit 16
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE)
{
LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size");
return;
}
// find message with msgID
auto it = m_IncompleteMessages.find (msgID);
if (it == m_IncompleteMessages.end ())
{
// create new message
auto msg = NewI2NPShortMessage ();
msg->len -= I2NP_SHORT_HEADER_SIZE;
it = m_IncompleteMessages.insert (std::make_pair (msgID,
std::unique_ptr<IncompleteMessage>(new IncompleteMessage (msg)))).first;
}
std::unique_ptr<IncompleteMessage>& incompleteMessage = it->second;
// handle current fragment
if (fragmentNum == incompleteMessage->nextFragmentNum)
{
// expected fragment
incompleteMessage->AttachNextFragment (buf, fragmentSize);
if (!isLast && !incompleteMessage->savedFragments.empty ())
{
// try saved fragments
for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();)
{
auto& savedFragment = *it1;
if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum)
{
incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len);
isLast = savedFragment->isLast;
incompleteMessage->savedFragments.erase (it1++);
}
else
break;
}
if (isLast)
LogPrint (eLogDebug, "SSU: Message ", msgID, " complete");
}
}
else
{
if (fragmentNum < incompleteMessage->nextFragmentNum)
// duplicate fragment
LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored");
else
{
// missing fragment
LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID);
auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast);
if (incompleteMessage->savedFragments.insert (std::unique_ptr<Fragment>(savedFragment)).second)
incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
else
LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved");
}
isLast = false;
}
if (isLast)
{
// delete incomplete message
auto msg = incompleteMessage->msg;
incompleteMessage->msg = nullptr;
m_IncompleteMessages.erase (msgID);
// process message
SendMsgAck (msgID);
msg->FromSSU (msgID);
if (m_Session.GetState () == eSessionStateEstablished)
{
if (!m_ReceivedMessages.count (msgID))
{
if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES)
m_ReceivedMessages.clear ();
else
ScheduleDecay ();
m_ReceivedMessages.insert (msgID);
if (!msg->IsExpired ())
m_Handler.PutNextMessage (msg);
else
LogPrint (eLogInfo, "SSU: message expired");
}
else
LogPrint (eLogWarning, "SSU: Message ", msgID, " already received");
}
else
{
// we expect DeliveryStatus
if (msg->GetTypeID () == eI2NPDeliveryStatus)
{
LogPrint (eLogDebug, "SSU: session established");
m_Session.Established ();
}
else
LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ());
}
}
else
SendFragmentAck (msgID, fragmentNum);
buf += fragmentSize;
}
}
void SSUData::FlushReceivedMessage ()
{
m_Handler.Flush ();
}
void SSUData::ProcessMessage (uint8_t * buf, size_t len)
{
//uint8_t * start = buf;
uint8_t flag = *buf;
buf++;
LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len);
// process acks if presented
if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED))
ProcessAcks (buf, flag);
// extended data if presented
if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED)
{
uint8_t extendedDataSize = *buf;
buf++; // size
LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present");
buf += extendedDataSize;
}
// process data
ProcessFragments (buf);
}
void SSUData::Send (std::shared_ptr<i2p::I2NPMessage> msg)
{
uint32_t msgID = msg->ToSSU ();
if (m_SentMessages.count (msgID) > 0)
{
LogPrint (eLogWarning, "SSU: message ", msgID, " already sent");
return;
}
if (m_SentMessages.empty ()) // schedule resend at first message only
ScheduleResend ();
auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr<SentMessage>(new SentMessage)));
std::unique_ptr<SentMessage>& sentMessage = ret.first->second;
if (ret.second)
{
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL;
sentMessage->numResends = 0;
}
auto& fragments = sentMessage->fragments;
size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3)
size_t len = msg->GetLength ();
uint8_t * msgBuf = msg->GetSSUHeader ();
uint32_t fragmentNum = 0;
while (len > 0)
{
Fragment * fragment = new Fragment;
fragment->fragmentNum = fragmentNum;
uint8_t * buf = fragment->buf;
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
payload++;
*payload = 1; // always 1 message fragment per message
payload++;
htobe32buf (payload, msgID);
payload += 4;
bool isLast = (len <= payloadSize);
size_t size = isLast ? len : payloadSize;
uint32_t fragmentInfo = (fragmentNum << 17);
if (isLast)
fragmentInfo |= 0x010000;
fragmentInfo |= size;
fragmentInfo = htobe32 (fragmentInfo);
memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3);
payload += 3;
memcpy (payload, msgBuf, size);
size += payload - buf;
if (size & 0x0F) // make sure 16 bytes boundary
size = ((size >> 4) + 1) << 4; // (/16 + 1)*16
fragment->len = size;
fragments.push_back (std::unique_ptr<Fragment> (fragment));
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size);
try
{
m_Session.Send (buf, size);
}
catch (boost::system::system_error& ec)
{
LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ());
}
if (!isLast)
{
len -= payloadSize;
msgBuf += payloadSize;
}
else
len = 0;
fragmentNum++;
}
}
void SSUData::SendMsgAck (uint32_t msgID)
{
uint8_t buf[48 + 18]; // actual length is 44 = 37 + 7 but pad it to multiple of 16
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACKs
payload++;
htobe32buf (payload, msgID); // msgID
payload += 4;
*payload = 0; // number of fragments
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48);
m_Session.Send (buf, 48);
}
void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum)
{
if (fragmentNum > 64)
{
LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64");
return;
}
uint8_t buf[64 + 18];
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACK bitfields
payload++;
// one ack
*(uint32_t *)(payload) = htobe32 (msgID); // msgID
payload += 4;
div_t d = div (fragmentNum, 7);
memset (payload, 0x80, d.quot); // 0x80 means non-last
payload += d.quot;
*payload = 0x01 << d.rem; // set corresponding bit
payload++;
*payload = 0; // number of fragments
size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1)
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len);
m_Session.Send (buf, len);
}
void SSUData::ScheduleResend()
{
m_ResendTimer.cancel ();
m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL));
auto s = m_Session.shared_from_this();
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleResendTimer (ecode); });
}
void SSUData::HandleResendTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();)
{
if (ts >= it->second->nextResendTime)
{
if (it->second->numResends < MAX_NUM_RESENDS)
{
for (auto& f: it->second->fragments)
if (f)
{
try
{
m_Session.Send (f->buf, f->len); // resend
}
catch (boost::system::system_error& ec)
{
LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ());
}
}
it->second->numResends++;
it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL;
it++;
}
else
{
LogPrint (eLogInfo, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted");
it = m_SentMessages.erase (it);
}
}
else
it++;
}
ScheduleResend ();
}
}
void SSUData::ScheduleDecay ()
{
m_DecayTimer.cancel ();
m_DecayTimer.expires_from_now (boost::posix_time::seconds(DECAY_INTERVAL));
auto s = m_Session.shared_from_this();
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleDecayTimer (ecode); });
}
void SSUData::HandleDecayTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
m_ReceivedMessages.clear ();
}
void SSUData::ScheduleIncompleteMessagesCleanup ()
{
m_IncompleteMessagesCleanupTimer.cancel ();
m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT));
auto s = m_Session.shared_from_this();
m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); });
}
void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
{
if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)
{
LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted");
it = m_IncompleteMessages.erase (it);
}
else
it++;
}
ScheduleIncompleteMessagesCleanup ();
}
}
}
}

128
SSUData.h
View File

@@ -1,128 +0,0 @@
#ifndef SSU_DATA_H__
#define SSU_DATA_H__
#include <inttypes.h>
#include <string.h>
#include <map>
#include <vector>
#include <set>
#include <memory>
#include <boost/asio.hpp>
#include "I2NPProtocol.h"
#include "Identity.h"
#include "RouterInfo.h"
namespace i2p
{
namespace transport
{
const size_t SSU_MTU_V4 = 1484;
const size_t SSU_MTU_V6 = 1472;
const size_t IPV4_HEADER_SIZE = 20;
const size_t IPV6_HEADER_SIZE = 40;
const size_t UDP_HEADER_SIZE = 8;
const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456
const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1424
const int RESEND_INTERVAL = 3; // in seconds
const int MAX_NUM_RESENDS = 5;
const int DECAY_INTERVAL = 20; // in seconds
const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check
// data flags
const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02;
const uint8_t DATA_FLAG_WANT_REPLY = 0x04;
const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08;
const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10;
const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40;
const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80;
struct Fragment
{
int fragmentNum;
size_t len;
bool isLast;
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest
Fragment () = default;
Fragment (int n, const uint8_t * b, int l, bool last):
fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); };
};
struct FragmentCmp
{
bool operator() (const std::unique_ptr<Fragment>& f1, const std::unique_ptr<Fragment>& f2) const
{
return f1->fragmentNum < f2->fragmentNum;
};
};
struct IncompleteMessage
{
std::shared_ptr<I2NPMessage> msg;
int nextFragmentNum;
uint32_t lastFragmentInsertTime; // in seconds
std::set<std::unique_ptr<Fragment>, FragmentCmp> savedFragments;
IncompleteMessage (std::shared_ptr<I2NPMessage> m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {};
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
};
struct SentMessage
{
std::vector<std::unique_ptr<Fragment> > fragments;
uint32_t nextResendTime; // in seconds
int numResends;
};
class SSUSession;
class SSUData
{
public:
SSUData (SSUSession& session);
~SSUData ();
void Start ();
void Stop ();
void ProcessMessage (uint8_t * buf, size_t len);
void FlushReceivedMessage ();
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
void AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter);
void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent);
private:
void SendMsgAck (uint32_t msgID);
void SendFragmentAck (uint32_t msgID, int fragmentNum);
void ProcessAcks (uint8_t *& buf, uint8_t flag);
void ProcessFragments (uint8_t * buf);
void ProcessSentMessageAck (uint32_t msgID);
void ScheduleResend ();
void HandleResendTimer (const boost::system::error_code& ecode);
void ScheduleDecay ();
void HandleDecayTimer (const boost::system::error_code& ecode);
void ScheduleIncompleteMessagesCleanup ();
void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode);
private:
SSUSession& m_Session;
std::map<uint32_t, std::unique_ptr<IncompleteMessage> > m_IncompleteMessages;
std::map<uint32_t, std::unique_ptr<SentMessage> > m_SentMessages;
std::set<uint32_t> m_ReceivedMessages;
boost::asio::deadline_timer m_ResendTimer, m_DecayTimer, m_IncompleteMessagesCleanupTimer;
int m_MaxPacketSize, m_PacketSize;
i2p::I2NPMessagesHandler m_Handler;
};
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,164 +0,0 @@
#ifndef SSU_SESSION_H__
#define SSU_SESSION_H__
#include <inttypes.h>
#include <set>
#include <memory>
#include "Crypto.h"
#include "I2NPProtocol.h"
#include "TransportSession.h"
#include "SSUData.h"
namespace i2p
{
namespace transport
{
const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04;
struct SSUHeader
{
uint8_t mac[16];
uint8_t iv[16];
uint8_t flag;
uint8_t time[4];
uint8_t GetPayloadType () const { return flag >> 4; };
bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; };
};
const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds
const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes
// payload types (4 bits)
const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0;
const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1;
const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2;
const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3;
const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4;
const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5;
const uint8_t PAYLOAD_TYPE_DATA = 6;
const uint8_t PAYLOAD_TYPE_PEER_TEST = 7;
const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8;
// extended options
const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001;
enum SessionState
{
eSessionStateUnknown,
eSessionStateIntroduced,
eSessionStateEstablished,
eSessionStateClosed,
eSessionStateFailed
};
enum PeerTestParticipant
{
ePeerTestParticipantUnknown = 0,
ePeerTestParticipantAlice1,
ePeerTestParticipantAlice2,
ePeerTestParticipantBob,
ePeerTestParticipantCharlie
};
class SSUServer;
class SSUSession: public TransportSession, public std::enable_shared_from_this<SSUSession>
{
public:
SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint,
std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, bool peerTest = false);
void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
~SSUSession ();
void Connect ();
void WaitForConnect ();
void Introduce (const i2p::data::RouterInfo::Introducer& introducer,
std::shared_ptr<const i2p::data::RouterInfo> to); // Alice to Charlie
void WaitForIntroduction ();
void Close ();
void Done ();
boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void SendPeerTest (); // Alice
SessionState GetState () const { return m_State; };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
void SendKeepAlive ();
uint32_t GetRelayTag () const { return m_RelayTag; };
const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; };
uint32_t GetCreationTime () const { return m_CreationTime; };
void FlushData ();
private:
boost::asio::io_service& GetService ();
void CreateAESandMacKey (const uint8_t * pubKey);
size_t GetSSUHeaderSize (const uint8_t * buf) const;
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session
void ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
void SendSessionRequest ();
void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce);
void ProcessSessionCreated (uint8_t * buf, size_t len);
void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true);
void ProcessSessionConfirmed (const uint8_t * buf, size_t len);
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from,
const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to);
void SendRelayIntro (std::shared_ptr<SSUSession> session, const boost::asio::ip::udp::endpoint& from);
void ProcessRelayResponse (const uint8_t * buf, size_t len);
void ProcessRelayIntro (const uint8_t * buf, size_t len);
void Established ();
void Failed ();
void ScheduleConnectTimer ();
void HandleConnectTimer (const boost::system::error_code& ecode);
void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true);
void ProcessData (uint8_t * buf, size_t len);
void SendSesionDestroyed ();
void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key
void Send (const uint8_t * buf, size_t size);
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey,
const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0);
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key
void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey);
void DecryptSessionKey (uint8_t * buf, size_t len);
bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey);
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
private:
friend class SSUData; // TODO: change in later
SSUServer& m_Server;
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
boost::asio::deadline_timer m_Timer;
bool m_IsPeerTest;
SessionState m_State;
bool m_IsSessionKey;
uint32_t m_RelayTag;
i2p::crypto::CBCEncryption m_SessionKeyEncryption;
i2p::crypto::CBCDecryption m_SessionKeyDecryption;
i2p::crypto::AESKey m_SessionKey;
i2p::crypto::MACKey m_MacKey;
i2p::data::RouterInfo::IntroKey m_IntroKey;
uint32_t m_CreationTime; // seconds since epoch
SSUData m_Data;
bool m_IsDataReceived;
std::unique_ptr<SignedData> m_SignedData; // we need it for SessionConfirmed only
std::map<uint32_t, std::shared_ptr<const i2p::data::RouterInfo> > m_RelayRequests; // nonce->Charlie
};
}
}
#endif

View File

@@ -1,491 +0,0 @@
#include <memory>
#include "Log.h"
#include "Signature.h"
namespace i2p
{
namespace crypto
{
class Ed25519
{
public:
Ed25519 ()
{
BN_CTX * ctx = BN_CTX_new ();
BIGNUM * tmp = BN_new ();
q = BN_new ();
// 2^255-19
BN_set_bit (q, 255); // 2^255
BN_sub_word (q, 19);
l = BN_new ();
// 2^252 + 27742317777372353535851937790883648493
BN_set_bit (l, 252);
two_252_2 = BN_dup (l);
BN_dec2bn (&tmp, "27742317777372353535851937790883648493");
BN_add (l, l, tmp);
BN_sub_word (two_252_2, 2); // 2^252 - 2
// -121665*inv(121666)
d = BN_new ();
BN_set_word (tmp, 121666);
BN_mod_inverse (tmp, tmp, q, ctx);
BN_set_word (d, 121665);
BN_set_negative (d, 1);
BN_mul (d, d, tmp, ctx);
// 2^((q-1)/4)
I = BN_new ();
BN_free (tmp);
tmp = BN_dup (q);
BN_sub_word (tmp, 1);
BN_div_word (tmp, 4);
BN_set_word (I, 2);
BN_mod_exp (I, I, tmp, q, ctx);
BN_free (tmp);
// 4*inv(5)
BIGNUM * By = BN_new ();
BN_set_word (By, 5);
BN_mod_inverse (By, By, q, ctx);
BN_mul_word (By, 4);
BIGNUM * Bx = RecoverX (By, ctx);
BN_mod (Bx, Bx, q, ctx); // % q
BN_mod (By, By, q, ctx); // % q
// precalculate Bi256 table
Bi256Carry = { Bx, By }; // B
for (int i = 0; i < 32; i++)
{
Bi256[i][0] = Bi256Carry; // first point
for (int j = 1; j < 128; j++)
Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B
Bi256Carry = Bi256[i][127];
for (int j = 0; j < 128; j++) // add first point 128 more times
Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx);
}
BN_CTX_free (ctx);
}
Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)),
d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)),
Bi256Carry (other.Bi256Carry)
{
for (int i = 0; i < 32; i++)
for (int j = 0; j < 128; j++)
Bi256[i][j] = other.Bi256[i][j];
}
~Ed25519 ()
{
BN_free (q);
BN_free (l);
BN_free (d);
BN_free (I);
BN_free (two_252_2);
}
EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const
{
return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian
}
EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const
{
return DecodePoint (buf, ctx);
}
void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const
{
EncodePoint (Normalize (publicKey, ctx), buf);
}
bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const
{
BN_CTX * ctx = BN_CTX_new ();
BIGNUM * h = DecodeBN<64> (digest);
// signature 0..31 - R, 32..63 - S
// B*S = R + PK*h => R = B*S - PK*h
// we don't decode R, but encode (B*S - PK*h)
auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S;
BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0
auto PKh = Mul (publicKey, h, ctx); // PK*h
uint8_t diff[32];
EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded
bool passed = !memcmp (signature, diff, 32); // R
BN_free (h);
BN_CTX_free (ctx);
if (!passed)
LogPrint (eLogError, "25519 signature verification failed");
return passed;
}
void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len,
uint8_t * signature) const
{
BN_CTX * bnCtx = BN_CTX_new ();
// calculate r
SHA512_CTX ctx;
SHA512_Init (&ctx);
SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key
SHA512_Update (&ctx, buf, len); // data
uint8_t digest[64];
SHA512_Final (digest, &ctx);
BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors
// calculate R
uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf
EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors
// calculate S
SHA512_Init (&ctx);
SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
SHA512_Update (&ctx, buf, len); // data
SHA512_Final (digest, &ctx);
BIGNUM * h = DecodeBN<64> (digest);
// S = (r + h*a) % l
BIGNUM * a = DecodeBN<EDDSA25519_PRIVATE_KEY_LENGTH> (expandedPrivateKey); // left half of expanded key
BN_mod_mul (h, h, a, l, bnCtx); // %l
BN_mod_add (h, h, r, l, bnCtx); // %l
memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2);
EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S
BN_free (r); BN_free (h); BN_free (a);
BN_CTX_free (bnCtx);
}
private:
EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const
{
// x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2)
// y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2)
// z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2)
// t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2)
BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new ();
BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2
BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2
BIGNUM * t1 = p1.t, * t2 = p2.t;
if (!t1) { t1 = BN_new (); BN_mul (t1, p1.x, p1.y, ctx); }
if (!t2) { t2 = BN_new (); BN_mul (t2, p2.x, p2.y, ctx); }
BN_mul (t3, t1, t2, ctx);
BN_mul (t3, t3, d, ctx); // C = d*t1*t2
if (!p1.t) BN_free (t1);
if (!p2.t) BN_free (t2);
if (p1.z)
{
if (p2.z)
BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2
else
BN_copy (z3, p1.z); // D = z1
}
else
{
if (p2.z)
BN_copy (z3, p2.z); // D = z2
else
BN_one (z3); // D = 1
}
BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new ();
BN_add (E, p1.x, p1.y);
BN_add (F, p2.x, p2.y);
BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2)
BN_sub (E, E, x3);
BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B
BN_sub (F, z3, t3); // F = D - C
BN_add (G, z3, t3); // G = D + C
BN_add (H, y3, x3); // H = B + A
BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F
BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H
BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G
BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H
BN_free (E); BN_free (F); BN_free (G); BN_free (H);
return EDDSAPoint {x3, y3, z3, t3};
}
EDDSAPoint Double (const EDDSAPoint& p, BN_CTX * ctx) const
{
BIGNUM * x2 = BN_new (), * y2 = BN_new (), * z2 = BN_new (), * t2 = BN_new ();
BN_sqr (x2, p.x, ctx); // x2 = A = x^2
BN_sqr (y2, p.y, ctx); // y2 = B = y^2
if (p.t)
BN_sqr (t2, p.t, ctx); // t2 = t^2
else
{
BN_mul (t2, p.x, p.y, ctx); // t = x*y
BN_sqr (t2, t2, ctx); // t2 = t^2
}
BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2
if (p.z)
BN_sqr (z2, p.z, ctx); // z2 = D = z^2
else
BN_one (z2); // z2 = 1
BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new ();
// E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy
BN_mul (E, p.x, p.y, ctx);
BN_lshift1 (E, E); // E =2*x*y
BN_sub (F, z2, t2); // F = D - C
BN_add (G, z2, t2); // G = D + C
BN_add (H, y2, x2); // H = B + A
BN_mod_mul (x2, E, F, q, ctx); // x2 = E*F
BN_mod_mul (y2, G, H, q, ctx); // y2 = G*H
BN_mod_mul (z2, F, G, q, ctx); // z2 = F*G
BN_mod_mul (t2, E, H, q, ctx); // t2 = E*H
BN_free (E); BN_free (F); BN_free (G); BN_free (H);
return EDDSAPoint {x2, y2, z2, t2};
}
EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const
{
BIGNUM * zero = BN_new (), * one = BN_new ();
BN_zero (zero); BN_one (one);
EDDSAPoint res {zero, one};
if (!BN_is_zero (e))
{
int bitCount = BN_num_bits (e);
for (int i = bitCount - 1; i >= 0; i--)
{
res = Double (res, ctx);
if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx);
}
}
return res;
}
EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian
{
BIGNUM * zero = BN_new (), * one = BN_new ();
BN_zero (zero); BN_one (one);
EDDSAPoint res {zero, one};
bool carry = false;
for (int i = 0; i < 32; i++)
{
uint8_t x = e[i];
if (carry)
{
if (x < 255)
{
x++;
carry = false;
}
else
x = 0;
}
if (x > 0)
{
if (x <= 128)
res = Sum (res, Bi256[i][x-1], ctx);
else
{
res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x]
carry = true;
}
}
}
if (carry) res = Sum (res, Bi256Carry, ctx);
return res;
}
EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const
{
if (p.z)
{
BIGNUM * x = BN_new (), * y = BN_new ();
BN_mod_inverse (y, p.z, q, ctx);
BN_mod_mul (x, p.x, y, q, ctx); // x = x/z
BN_mod_mul (y, p.y, y, q, ctx); // y = y/z
return EDDSAPoint{x, y};
}
else
return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)};
}
bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const
{
BIGNUM * x2 = BN_new ();
BN_sqr (x2, p.x, ctx); // x^2
BIGNUM * y2 = BN_new ();
BN_sqr (y2, p.y, ctx); // y^2
// y^2 - x^2 - 1 - d*x^2*y^2
BIGNUM * tmp = BN_new ();
BN_mul (tmp, d, x2, ctx);
BN_mul (tmp, tmp, y2, ctx);
BN_sub (tmp, y2, tmp);
BN_sub (tmp, tmp, x2);
BN_sub_word (tmp, 1);
BN_mod (tmp, tmp, q, ctx); // % q
bool ret = BN_is_zero (tmp);
BN_free (x2);
BN_free (y2);
BN_free (tmp);
return ret;
}
BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const
{
BIGNUM * y2 = BN_new ();
BN_sqr (y2, y, ctx); // y^2
// xx = (y^2 -1)*inv(d*y^2 +1)
BIGNUM * xx = BN_new ();
BN_mul (xx, d, y2, ctx);
BN_add_word (xx, 1);
BN_mod_inverse (xx, xx, q, ctx);
BN_sub_word (y2, 1);
BN_mul (xx, y2, xx, ctx);
// x = srqt(xx) = xx^(2^252-2)
BIGNUM * x = BN_new ();
BN_mod_exp (x, xx, two_252_2, q, ctx);
// check (x^2 -xx) % q
BN_sqr (y2, x, ctx);
BN_mod_sub (y2, y2, xx, q, ctx);
if (!BN_is_zero (y2))
BN_mod_mul (x, x, I, q, ctx);
if (BN_is_odd (x))
BN_sub (x, q, x);
BN_free (y2);
BN_free (xx);
return x;
}
EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const
{
// buf is 32 bytes Little Endian, convert it to Big Endian
uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH];
for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes
{
buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i];
buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i];
}
bool isHighestBitSet = buf1[0] & 0x80;
if (isHighestBitSet)
buf1[0] &= 0x7f; // clear highest bit
BIGNUM * y = BN_new ();
BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y);
auto x = RecoverX (y, ctx);
if (BN_is_bit_set (x, 0) != isHighestBitSet)
BN_sub (x, q, x); // x = q - x
BIGNUM * z = BN_new (), * t = BN_new ();
BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t
EDDSAPoint p {x, y, z, t};
if (!IsOnCurve (p, ctx))
LogPrint (eLogError, "Decoded point is not on 25519");
return p;
}
void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const
{
EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH);
if (BN_is_bit_set (p.x, 0)) // highest bit
buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit
}
template<int len>
BIGNUM * DecodeBN (const uint8_t * buf) const
{
// buf is Little Endian convert it to Big Endian
uint8_t buf1[len];
for (size_t i = 0; i < len/2; i++) // invert bytes
{
buf1[i] = buf[len -1 - i];
buf1[len -1 - i] = buf[i];
}
BIGNUM * res = BN_new ();
BN_bin2bn (buf1, len, res);
return res;
}
void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const
{
bn2buf (bn, buf, len);
// To Little Endian
for (size_t i = 0; i < len/2; i++) // invert bytes
{
uint8_t tmp = buf[i];
buf[i] = buf[len -1 - i];
buf[len -1 - i] = tmp;
}
}
private:
BIGNUM * q, * l, * d, * I;
// transient values
BIGNUM * two_252_2; // 2^252-2
EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes
// if j > 128 we use 256 - j and carry 1 to next byte
// Bi256[0][0] = B, base point
EDDSAPoint Bi256Carry; // Bi256[32][0]
};
static std::unique_ptr<Ed25519> g_Ed25519;
std::unique_ptr<Ed25519>& GetEd25519 ()
{
if (!g_Ed25519)
{
auto c = new Ed25519();
if (!g_Ed25519) // make sure it was not created already
g_Ed25519.reset (c);
else
delete c;
}
return g_Ed25519;
}
EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey)
{
memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH);
BN_CTX * ctx = BN_CTX_new ();
m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx);
BN_CTX_free (ctx);
}
bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
uint8_t digest[64];
SHA512_CTX ctx;
SHA512_Init (&ctx);
SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R
SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
SHA512_Update (&ctx, buf, len); // data
SHA512_Final (digest, &ctx);
return GetEd25519 ()->Verify (m_PublicKey, digest, signature);
}
EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey)
{
// expand key
SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey);
m_ExpandedPrivateKey[0] &= 0xF8; // drop last 3 bits
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x1F; // drop first 3 bits
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit
// generate and encode public key
BN_CTX * ctx = BN_CTX_new ();
auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx);
GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx);
BN_CTX_free (ctx);
}
void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature);
}
}
}

View File

@@ -1,442 +0,0 @@
#ifndef SIGNATURE_H__
#define SIGNATURE_H__
#include <inttypes.h>
#include <string.h>
#include <openssl/dsa.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include "Crypto.h"
namespace i2p
{
namespace crypto
{
class Verifier
{
public:
virtual ~Verifier () {};
virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0;
virtual size_t GetPublicKeyLen () const = 0;
virtual size_t GetSignatureLen () const = 0;
virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; };
};
class Signer
{
public:
virtual ~Signer () {};
virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
};
const size_t DSA_PUBLIC_KEY_LENGTH = 128;
const size_t DSA_SIGNATURE_LENGTH = 40;
const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
class DSAVerifier: public Verifier
{
public:
DSAVerifier (const uint8_t * signingKey)
{
m_PublicKey = CreateDSA ();
m_PublicKey->pub_key = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL);
}
~DSAVerifier ()
{
DSA_free (m_PublicKey);
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
sig->r = BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL);
sig->s = BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL);
// DSA verification
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
DSA_SIG_free(sig);
return ret;
}
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
private:
DSA * m_PublicKey;
};
class DSASigner: public Signer
{
public:
DSASigner (const uint8_t * signingPrivateKey)
{
m_PrivateKey = CreateDSA ();
m_PrivateKey->priv_key = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL);
}
~DSASigner ()
{
DSA_free (m_PrivateKey);
}
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20];
SHA1 (buf, len, digest);
DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
bn2buf (sig->r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (sig->s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
}
private:
DSA * m_PrivateKey;
};
inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
DSA * dsa = CreateDSA ();
DSA_generate_key (dsa);
bn2buf (dsa->priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
bn2buf (dsa->pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
DSA_free (dsa);
}
struct SHA256Hash
{
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
{
SHA256 (buf, len, digest);
}
enum { hashLen = 32 };
};
struct SHA384Hash
{
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
{
SHA384 (buf, len, digest);
}
enum { hashLen = 48 };
};
struct SHA512Hash
{
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
{
SHA512 (buf, len, digest);
}
enum { hashLen = 64 };
};
template<typename Hash, int curve, size_t keyLen>
class ECDSAVerifier: public Verifier
{
public:
ECDSAVerifier (const uint8_t * signingKey)
{
m_PublicKey = EC_KEY_new_by_curve_name (curve);
EC_KEY_set_public_key_affine_coordinates (m_PublicKey,
BN_bin2bn (signingKey, keyLen/2, NULL),
BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL));
}
~ECDSAVerifier ()
{
EC_KEY_free (m_PublicKey);
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
uint8_t digest[Hash::hashLen];
Hash::CalculateHash (buf, len, digest);
ECDSA_SIG * sig = ECDSA_SIG_new();
sig->r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL);
sig->s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL);
// ECDSA verification
int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey);
ECDSA_SIG_free(sig);
return ret;
}
size_t GetPublicKeyLen () const { return keyLen; };
size_t GetSignatureLen () const { return keyLen; }; // signature length = key length
private:
EC_KEY * m_PublicKey;
};
template<typename Hash, int curve, size_t keyLen>
class ECDSASigner: public Signer
{
public:
ECDSASigner (const uint8_t * signingPrivateKey)
{
m_PrivateKey = EC_KEY_new_by_curve_name (curve);
EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL));
}
~ECDSASigner ()
{
EC_KEY_free (m_PrivateKey);
}
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[Hash::hashLen];
Hash::CalculateHash (buf, len, digest);
ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey);
// signatureLen = keyLen
bn2buf (sig->r, signature, keyLen/2);
bn2buf (sig->s, signature + keyLen/2, keyLen/2);
ECDSA_SIG_free(sig);
}
private:
EC_KEY * m_PrivateKey;
};
inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve);
EC_KEY_generate_key (signingKey);
bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2);
BIGNUM * x = BN_new(), * y = BN_new();
EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey),
EC_KEY_get0_public_key (signingKey), x, y, NULL);
bn2buf (x, signingPublicKey, keyLen/2);
bn2buf (y, signingPublicKey + keyLen/2, keyLen/2);
BN_free (x); BN_free (y);
EC_KEY_free (signingKey);
}
// ECDSA_SHA256_P256
const size_t ECDSAP256_KEY_LENGTH = 64;
typedef ECDSAVerifier<SHA256Hash, NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH> ECDSAP256Verifier;
typedef ECDSASigner<SHA256Hash, NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH> ECDSAP256Signer;
inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey);
}
// ECDSA_SHA384_P384
const size_t ECDSAP384_KEY_LENGTH = 96;
typedef ECDSAVerifier<SHA384Hash, NID_secp384r1, ECDSAP384_KEY_LENGTH> ECDSAP384Verifier;
typedef ECDSASigner<SHA384Hash, NID_secp384r1, ECDSAP384_KEY_LENGTH> ECDSAP384Signer;
inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey);
}
// ECDSA_SHA512_P521
const size_t ECDSAP521_KEY_LENGTH = 132;
typedef ECDSAVerifier<SHA512Hash, NID_secp521r1, ECDSAP521_KEY_LENGTH> ECDSAP521Verifier;
typedef ECDSASigner<SHA512Hash, NID_secp521r1, ECDSAP521_KEY_LENGTH> ECDSAP521Signer;
inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey);
}
// RSA
template<typename Hash, int type, size_t keyLen>
class RSAVerifier: public Verifier
{
public:
RSAVerifier (const uint8_t * signingKey)
{
m_PublicKey = RSA_new ();
memset (m_PublicKey, 0, sizeof (RSA));
m_PublicKey->e = BN_dup (GetRSAE ());
m_PublicKey->n = BN_bin2bn (signingKey, keyLen, NULL);
}
~RSAVerifier ()
{
RSA_free (m_PublicKey);
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
uint8_t digest[Hash::hashLen];
Hash::CalculateHash (buf, len, digest);
return RSA_verify (type, digest, Hash::hashLen, signature, GetSignatureLen (), m_PublicKey);
}
size_t GetPublicKeyLen () const { return keyLen; }
size_t GetSignatureLen () const { return keyLen; }
size_t GetPrivateKeyLen () const { return GetSignatureLen ()*2; };
private:
RSA * m_PublicKey;
};
template<typename Hash, int type, size_t keyLen>
class RSASigner: public Signer
{
public:
RSASigner (const uint8_t * signingPrivateKey)
{
m_PrivateKey = RSA_new ();
memset (m_PrivateKey, 0, sizeof (RSA));
m_PrivateKey->e = BN_dup (GetRSAE ());
m_PrivateKey->n = BN_bin2bn (signingPrivateKey, keyLen, NULL);
m_PrivateKey->d = BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL);
}
~RSASigner ()
{
RSA_free (m_PrivateKey);
}
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[Hash::hashLen];
Hash::CalculateHash (buf, len, digest);
unsigned int signatureLen = keyLen;
RSA_sign (type, digest, Hash::hashLen, signature, &signatureLen, m_PrivateKey);
}
private:
RSA * m_PrivateKey;
};
inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
RSA * rsa = RSA_new ();
BIGNUM * e = BN_dup (GetRSAE ()); // make it non-const
RSA_generate_key_ex (rsa, publicKeyLen*8, e, NULL);
bn2buf (rsa->n, signingPrivateKey, publicKeyLen);
bn2buf (rsa->d, signingPrivateKey + publicKeyLen, publicKeyLen);
bn2buf (rsa->n, signingPublicKey, publicKeyLen);
BN_free (e); // this e is not assigned to rsa->e
RSA_free (rsa);
}
// RSA_SHA256_2048
const size_t RSASHA2562048_KEY_LENGTH = 256;
typedef RSAVerifier<SHA256Hash, NID_sha256, RSASHA2562048_KEY_LENGTH> RSASHA2562048Verifier;
typedef RSASigner<SHA256Hash, NID_sha256, RSASHA2562048_KEY_LENGTH> RSASHA2562048Signer;
// RSA_SHA384_3072
const size_t RSASHA3843072_KEY_LENGTH = 384;
typedef RSAVerifier<SHA384Hash, NID_sha384, RSASHA3843072_KEY_LENGTH> RSASHA3843072Verifier;
typedef RSASigner<SHA384Hash, NID_sha384, RSASHA3843072_KEY_LENGTH> RSASHA3843072Signer;
// RSA_SHA512_4096
const size_t RSASHA5124096_KEY_LENGTH = 512;
typedef RSAVerifier<SHA512Hash, NID_sha512, RSASHA5124096_KEY_LENGTH> RSASHA5124096Verifier;
typedef RSASigner<SHA512Hash, NID_sha512, RSASHA5124096_KEY_LENGTH> RSASHA5124096Signer;
// EdDSA
struct EDDSAPoint
{
BIGNUM * x, * y;
BIGNUM * z, * t; // projective coordinates
EDDSAPoint (): x(nullptr), y(nullptr), z(nullptr), t(nullptr) {};
EDDSAPoint (const EDDSAPoint& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr)
{ *this = other; };
EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr)
{ *this = std::move (other); };
EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr): x(x1), y(y1), z(z1), t(t1) {};
~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); };
EDDSAPoint& operator=(EDDSAPoint&& other)
{
if (x) BN_free (x); x = other.x; other.x = nullptr;
if (y) BN_free (y); y = other.y; other.y = nullptr;
if (z) BN_free (z); z = other.z; other.z = nullptr;
if (t) BN_free (t); t = other.t; other.t = nullptr;
return *this;
}
EDDSAPoint& operator=(const EDDSAPoint& other)
{
if (x) BN_free (x); x = other.x ? BN_dup (other.x) : nullptr;
if (y) BN_free (y); y = other.y ? BN_dup (other.y) : nullptr;
if (z) BN_free (z); z = other.z ? BN_dup (other.z) : nullptr;
if (t) BN_free (t); t = other.t ? BN_dup (other.t) : nullptr;
return *this;
}
EDDSAPoint operator-() const
{
BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL;
if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); };
if (y) y1 = BN_dup (y);
if (z) z1 = BN_dup (z);
if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); };
return EDDSAPoint {x1, y1, z1, t1};
}
};
const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32;
const size_t EDDSA25519_SIGNATURE_LENGTH = 64;
const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32;
class EDDSA25519Verifier: public Verifier
{
public:
EDDSA25519Verifier (const uint8_t * signingKey);
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; };
private:
EDDSAPoint m_PublicKey;
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
};
class EDDSA25519Signer: public Signer
{
public:
EDDSA25519Signer (const uint8_t * signingPrivateKey);
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; };
private:
uint8_t m_ExpandedPrivateKey[64];
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
};
inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH);
EDDSA25519Signer signer (signingPrivateKey);
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
}
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,280 +0,0 @@
#ifndef STREAMING_H__
#define STREAMING_H__
#include <inttypes.h>
#include <string>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <functional>
#include <memory>
#include <mutex>
#include <boost/asio.hpp>
#include "Base.h"
#include "I2PEndian.h"
#include "Identity.h"
#include "LeaseSet.h"
#include "I2NPProtocol.h"
#include "Garlic.h"
#include "Tunnel.h"
namespace i2p
{
namespace client
{
class ClientDestination;
}
namespace stream
{
const uint16_t PACKET_FLAG_SYNCHRONIZE = 0x0001;
const uint16_t PACKET_FLAG_CLOSE = 0x0002;
const uint16_t PACKET_FLAG_RESET = 0x0004;
const uint16_t PACKET_FLAG_SIGNATURE_INCLUDED = 0x0008;
const uint16_t PACKET_FLAG_SIGNATURE_REQUESTED = 0x0010;
const uint16_t PACKET_FLAG_FROM_INCLUDED = 0x0020;
const uint16_t PACKET_FLAG_DELAY_REQUESTED = 0x0040;
const uint16_t PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED = 0x0080;
const uint16_t PACKET_FLAG_PROFILE_INTERACTIVE = 0x0100;
const uint16_t PACKET_FLAG_ECHO = 0x0200;
const uint16_t PACKET_FLAG_NO_ACK = 0x0400;
const size_t STREAMING_MTU = 1730;
const size_t MAX_PACKET_SIZE = 4096;
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
const int ACK_SEND_TIMEOUT = 200; // in milliseconds
const int MAX_NUM_RESEND_ATTEMPTS = 6;
const int WINDOW_SIZE = 6; // in messages
const int MIN_WINDOW_SIZE = 1;
const int MAX_WINDOW_SIZE = 128;
const int INITIAL_RTT = 8000; // in milliseconds
const int INITIAL_RTO = 9000; // in milliseconds
const size_t MAX_PENDING_INCOMING_BACKLOG = 128;
const int PENDING_INCOMING_TIMEOUT = 10; // in seconds
struct Packet
{
size_t len, offset;
uint8_t buf[MAX_PACKET_SIZE];
uint64_t sendTime;
Packet (): len (0), offset (0), sendTime (0) {};
uint8_t * GetBuffer () { return buf + offset; };
size_t GetLength () const { return len - offset; };
uint32_t GetSendStreamID () const { return bufbe32toh (buf); };
uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); };
uint32_t GetSeqn () const { return bufbe32toh (buf + 8); };
uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); };
uint8_t GetNACKCount () const { return buf[16]; };
uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); };
const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags
uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); };
uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); };
const uint8_t * GetOptionData () const { return GetOption () + 2; };
const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); };
bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; };
bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; };
};
struct PacketCmp
{
bool operator() (const Packet * p1, const Packet * p2) const
{
return p1->GetSeqn () < p2->GetSeqn ();
};
};
enum StreamStatus
{
eStreamStatusNew = 0,
eStreamStatusOpen,
eStreamStatusReset,
eStreamStatusClosing,
eStreamStatusClosed
};
class StreamingDestination;
class Stream: public std::enable_shared_from_this<Stream>
{
public:
typedef std::function<void (const boost::system::error_code& ecode)> SendHandler;
Stream (boost::asio::io_service& service, StreamingDestination& local,
std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0); // outgoing
Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming
~Stream ();
uint32_t GetSendStreamID () const { return m_SendStreamID; };
uint32_t GetRecvStreamID () const { return m_RecvStreamID; };
std::shared_ptr<const i2p::data::LeaseSet> GetRemoteLeaseSet () const { return m_RemoteLeaseSet; };
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () const { return m_RemoteIdentity; };
bool IsOpen () const { return m_Status == eStreamStatusOpen; };
bool IsEstablished () const { return m_SendStreamID; };
StreamStatus GetStatus () const { return m_Status; };
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
void HandleNextPacket (Packet * packet);
size_t Send (const uint8_t * buf, size_t len);
void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler);
template<typename Buffer, typename ReceiveHandler>
void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0);
size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
void Close ();
void Cancel () { m_ReceiveTimer.cancel (); };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
size_t GetSendQueueSize () const { return m_SentPackets.size (); };
size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); };
size_t GetSendBufferSize () const { return m_SendBuffer.rdbuf ()->in_avail (); };
int GetWindowSize () const { return m_WindowSize; };
int GetRTT () const { return m_RTT; };
private:
void Terminate ();
void SendBuffer ();
void SendQuickAck ();
void SendClose ();
bool SendPacket (Packet * packet);
void SendPackets (const std::vector<Packet *>& packets);
void SavePacket (Packet * packet);
void ProcessPacket (Packet * packet);
void ProcessAck (Packet * packet);
size_t ConcatenatePackets (uint8_t * buf, size_t len);
void UpdateCurrentRemoteLease (bool expired = false);
template<typename Buffer, typename ReceiveHandler>
void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler);
void ScheduleResend ();
void HandleResendTimer (const boost::system::error_code& ecode);
void HandleAckSendTimer (const boost::system::error_code& ecode);
private:
boost::asio::io_service& m_Service;
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
int32_t m_LastReceivedSequenceNumber;
StreamStatus m_Status;
bool m_IsAckSendScheduled;
StreamingDestination& m_LocalDestination;
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
std::shared_ptr<const i2p::data::Lease> m_CurrentRemoteLease;
std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
std::queue<Packet *> m_ReceiveQueue;
std::set<Packet *, PacketCmp> m_SavedPackets;
std::set<Packet *, PacketCmp> m_SentPackets;
boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer;
size_t m_NumSentBytes, m_NumReceivedBytes;
uint16_t m_Port;
std::mutex m_SendBufferMutex;
std::stringstream m_SendBuffer;
int m_WindowSize, m_RTT, m_RTO;
uint64_t m_LastWindowSizeIncreaseTime;
int m_NumResendAttempts;
SendHandler m_SendHandler;
};
class StreamingDestination: public std::enable_shared_from_this<StreamingDestination>
{
public:
typedef std::function<void (std::shared_ptr<Stream>)> Acceptor;
StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort = 0, bool gzip = true);
~StreamingDestination ();
void Start ();
void Stop ();
std::shared_ptr<Stream> CreateNewOutgoingStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
void DeleteStream (std::shared_ptr<Stream> stream);
void SetAcceptor (const Acceptor& acceptor);
void ResetAcceptor ();
bool IsAcceptorSet () const { return m_Acceptor != nullptr; };
std::shared_ptr<i2p::client::ClientDestination> GetOwner () const { return m_Owner; };
uint16_t GetLocalPort () const { return m_LocalPort; };
void HandleDataMessagePayload (const uint8_t * buf, size_t len);
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort);
private:
void HandleNextPacket (Packet * packet);
std::shared_ptr<Stream> CreateNewIncomingStream ();
void HandlePendingIncomingTimer (const boost::system::error_code& ecode);
private:
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
uint16_t m_LocalPort;
bool m_Gzip; // gzip compression of data messages
std::mutex m_StreamsMutex;
std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream
Acceptor m_Acceptor;
std::list<std::shared_ptr<Stream> > m_PendingIncomingStreams;
boost::asio::deadline_timer m_PendingIncomingTimer;
std::map<uint32_t, std::list<Packet *> > m_SavedPackets; // receiveStreamID->packets, arrived before SYN
public:
i2p::data::GzipInflator m_Inflator;
i2p::data::GzipDeflator m_Deflator;
// for HTTP only
const decltype(m_Streams)& GetStreams () const { return m_Streams; };
};
//-------------------------------------------------
template<typename Buffer, typename ReceiveHandler>
void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
{
auto s = shared_from_this();
m_Service.post ([=](void)
{
if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset)
s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler);
else
{
s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout));
s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode)
{ s->HandleReceiveTimer (ecode, buffer, handler); });
}
});
}
template<typename Buffer, typename ReceiveHandler>
void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler)
{
size_t received = ConcatenatePackets (boost::asio::buffer_cast<uint8_t *>(buffer), boost::asio::buffer_size(buffer));
if (received > 0)
handler (boost::system::error_code (), received);
else if (ecode == boost::asio::error::operation_aborted)
{
// timeout not expired
if (m_Status == eStreamStatusReset)
handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0);
else
handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0);
}
else
// timeout expired
handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received);
}
}
}
#endif

16
TODO Normal file
View File

@@ -0,0 +1,16 @@
Refactoring:
- SSUSession:637, SSUSession:635 get rid of casting to SSUHeader
- Identity.cpp:156 check for self asignment
- Rely on a library for TLS and SSL.
- Move parsing code out of networking code, to allow better testing.
- Move streaming code to a separate directory.
- Separate front-end code (SAM, BOB, ...) from the back-end.
Additions:
- Write tests.
- Add documentation.
- Create a specialized IRC tunnel (instead of just a generic ClientTunnel instance).
- Ban peers with unexpected / bad behaviour.
- EdDSA support.
- Return correct error codes in HTTPProxy.
- Modify web interface layout and use AJAX to update information.

View File

@@ -1,32 +0,0 @@
#ifndef TIMESTAMP_H__
#define TIMESTAMP_H__
#include <inttypes.h>
#include <chrono>
namespace i2p
{
namespace util
{
inline uint64_t GetMillisecondsSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
inline uint32_t GetHoursSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::hours>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
inline uint64_t GetSecondsSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
}
}
#endif

View File

@@ -1,110 +0,0 @@
#include <string.h>
#include "I2PEndian.h"
#include "Log.h"
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "Tunnel.h"
#include "Transports.h"
#include "TransitTunnel.h"
namespace i2p
{
namespace tunnel
{
TransitTunnel::TransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TunnelBase (receiveTunnelID, nextTunnelID, nextIdent)
{
m_Encryption.SetKeys (layerKey, ivKey);
}
void TransitTunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
{
m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
}
TransitTunnelParticipant::~TransitTunnelParticipant ()
{
}
void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (tunnelMsg, newMsg);
m_NumTransmittedBytes += tunnelMsg->GetLength ();
htobe32buf (newMsg->GetPayload (), GetNextTunnelID ());
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
m_TunnelDataMsgs.push_back (newMsg);
}
void TransitTunnelParticipant::FlushTunnelDataMsgs ()
{
if (!m_TunnelDataMsgs.empty ())
{
auto num = m_TunnelDataMsgs.size ();
if (num > 1)
LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num);
i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
m_TunnelDataMsgs.clear ();
}
}
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
}
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
}
void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
TunnelMessageBlock block;
block.deliveryType = eDeliveryTypeLocal;
block.data = msg;
std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.PutTunnelDataMsg (block);
}
void TransitTunnelGateway::FlushTunnelDataMsgs ()
{
std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.SendBuffer ();
}
void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (tunnelMsg, newMsg);
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
}
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey,
bool isGateway, bool isEndpoint)
{
if (isEndpoint)
{
LogPrint (eLogInfo, "TransitTunnel: endpoint ", receiveTunnelID, " created");
return std::make_shared<TransitTunnelEndpoint> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
else if (isGateway)
{
LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created");
return std::make_shared<TransitTunnelGateway> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
else
{
LogPrint (eLogInfo, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created");
return std::make_shared<TransitTunnelParticipant> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
}
}
}

View File

@@ -1,103 +0,0 @@
#ifndef TRANSIT_TUNNEL_H__
#define TRANSIT_TUNNEL_H__
#include <inttypes.h>
#include <vector>
#include <mutex>
#include <memory>
#include "Crypto.h"
#include "I2NPProtocol.h"
#include "TunnelEndpoint.h"
#include "TunnelGateway.h"
#include "TunnelBase.h"
namespace i2p
{
namespace tunnel
{
class TransitTunnel: public TunnelBase
{
public:
TransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey);
virtual size_t GetNumTransmittedBytes () const { return 0; };
// implements TunnelBase
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
private:
i2p::crypto::TunnelEncryption m_Encryption;
};
class TransitTunnelParticipant: public TransitTunnel
{
public:
TransitTunnelParticipant (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_NumTransmittedBytes (0) {};
~TransitTunnelParticipant ();
size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; };
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
void FlushTunnelDataMsgs ();
private:
size_t m_NumTransmittedBytes;
std::vector<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
};
class TransitTunnelGateway: public TransitTunnel
{
public:
TransitTunnelGateway (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_Gateway(this) {};
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void FlushTunnelDataMsgs ();
size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); };
private:
std::mutex m_SendMutex;
TunnelGateway m_Gateway;
};
class TransitTunnelEndpoint: public TransitTunnel
{
public:
TransitTunnelEndpoint (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }
private:
TunnelEndpoint m_Endpoint;
};
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey,
bool isGateway, bool isEndpoint);
}
}
#endif

View File

@@ -1,85 +0,0 @@
#ifndef TRANSPORT_SESSION_H__
#define TRANSPORT_SESSION_H__
#include <inttypes.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Identity.h"
#include "Crypto.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
namespace i2p
{
namespace transport
{
class SignedData
{
public:
SignedData () {}
SignedData (const SignedData& other)
{
m_Stream << other.m_Stream.rdbuf ();
}
void Insert (const uint8_t * buf, size_t len)
{
m_Stream.write ((char *)buf, len);
}
template<typename T>
void Insert (T t)
{
m_Stream.write ((char *)&t, sizeof (T));
}
bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
{
return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
}
void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
{
keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
}
private:
std::stringstream m_Stream;
};
class TransportSession
{
public:
TransportSession (std::shared_ptr<const i2p::data::RouterInfo> router):
m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router)
{
if (router)
m_RemoteIdentity = router->GetRouterIdentity ();
}
virtual ~TransportSession () {};
virtual void Done () = 0;
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () { return m_RemoteIdentity; };
void SetRemoteIdentity (std::shared_ptr<const i2p::data::IdentityEx> ident) { m_RemoteIdentity = ident; };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
bool IsOutgoing () const { return m_IsOutgoing; };
virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
protected:
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
std::shared_ptr<i2p::crypto::DHKeys> m_DHKeysPair; // X - for client and Y - for server
size_t m_NumSentBytes, m_NumReceivedBytes;
bool m_IsOutgoing;
};
}
}
#endif

View File

@@ -1,622 +0,0 @@
#include "Log.h"
#include "Crypto.h"
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "NetDb.h"
#include "Transports.h"
using namespace i2p::data;
namespace i2p
{
namespace transport
{
DHKeysPairSupplier::DHKeysPairSupplier (int size):
m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr)
{
}
DHKeysPairSupplier::~DHKeysPairSupplier ()
{
Stop ();
}
void DHKeysPairSupplier::Start ()
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this));
}
void DHKeysPairSupplier::Stop ()
{
m_IsRunning = false;
m_Acquired.notify_one ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
}
}
void DHKeysPairSupplier::Run ()
{
while (m_IsRunning)
{
int num;
while ((num = m_QueueSize - m_Queue.size ()) > 0)
CreateDHKeysPairs (num);
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Acquired.wait (l); // wait for element gets aquired
}
}
void DHKeysPairSupplier::CreateDHKeysPairs (int num)
{
if (num > 0)
{
i2p::crypto::DHKeys dh;
for (int i = 0; i < num; i++)
{
auto pair = std::make_shared<i2p::crypto::DHKeys> ();
pair->GenerateKeys ();
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Queue.push (pair);
}
}
}
std::shared_ptr<i2p::crypto::DHKeys> DHKeysPairSupplier::Acquire ()
{
{
std::unique_lock<std::mutex> l(m_AcquiredMutex);
if (!m_Queue.empty ())
{
auto pair = m_Queue.front ();
m_Queue.pop ();
m_Acquired.notify_one ();
return pair;
}
}
// queue is empty, create new
auto pair = std::make_shared<i2p::crypto::DHKeys> ();
pair->GenerateKeys ();
return pair;
}
void DHKeysPairSupplier::Return (std::shared_ptr<i2p::crypto::DHKeys> pair)
{
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Queue.push (pair);
}
Transports transports;
Transports::Transports ():
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service),
m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0)
{
}
Transports::~Transports ()
{
Stop ();
}
void Transports::Start ()
{
m_DHKeysPairSupplier.Start ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Transports::Run, this));
// create acceptors
auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (auto address : addresses)
{
if (!m_NTCPServer)
{
m_NTCPServer = new NTCPServer ();
m_NTCPServer->Start ();
}
if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ())
{
if (!m_SSUServer)
{
m_SSUServer = new SSUServer (address->port);
LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port);
m_SSUServer->Start ();
DetectExternalIP ();
}
else
LogPrint (eLogError, "Transports: SSU server already exists");
}
}
m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
}
void Transports::Stop ()
{
m_PeerCleanupTimer.cancel ();
m_Peers.clear ();
if (m_SSUServer)
{
m_SSUServer->Stop ();
delete m_SSUServer;
m_SSUServer = nullptr;
}
if (m_NTCPServer)
{
m_NTCPServer->Stop ();
delete m_NTCPServer;
m_NTCPServer = nullptr;
}
m_DHKeysPairSupplier.Stop ();
m_IsRunning = false;
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void Transports::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Transports: runtime exception: ", ex.what ());
}
}
}
void Transports::UpdateBandwidth ()
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
if (m_LastBandwidthUpdateTime > 0)
{
auto delta = ts - m_LastBandwidthUpdateTime;
if (delta > 0)
{
m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second
m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second
}
}
m_LastBandwidthUpdateTime = ts;
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
}
bool Transports::IsBandwidthExceeded () const
{
auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes
auto bw = std::max (m_InBandwidth, m_OutBandwidth);
return bw > limit;
}
void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
{
SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });
}
void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
{
m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs));
}
void Transports::PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs)
{
if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
{
// we send it to ourself
for (auto it: msgs)
i2p::HandleI2NPMessage (it);
return;
}
auto it = m_Peers.find (ident);
if (it == m_Peers.end ())
{
bool connected = false;
try
{
auto r = netdb.FindRouter (ident);
{
std::unique_lock<std::mutex> l(m_PeersMutex);
it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, { 0, r, {},
i2p::util::GetSecondsSinceEpoch (), {} })).first;
}
connected = ConnectToPeer (ident, it->second);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ());
}
if (!connected) return;
}
if (!it->second.sessions.empty ())
it->second.sessions.front ()->SendI2NPMessages (msgs);
else
{
for (auto it1: msgs)
it->second.delayedMessages.push_back (it1);
}
}
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer)
{
if (peer.router) // we have RI already
{
if (!peer.numAttempts) // NTCP
{
peer.numAttempts++;
auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ());
if (address && m_NTCPServer)
{
#if BOOST_VERSION >= 104900
if (!address->host.is_unspecified ()) // we have address now
#else
boost::system::error_code ecode;
address->host.to_string (ecode);
if (!ecode)
#endif
{
if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ())
{
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
m_NTCPServer->Connect (address->host, address->port, s);
return true;
}
}
else // we don't have address
{
if (address->addressString.length () > 0) // trying to resolve
{
LogPrint (eLogDebug, "Transports: Resolving NTCP ", address->addressString);
NTCPResolve (address->addressString, ident);
return true;
}
}
}
else
LogPrint (eLogWarning, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU");
}
if (peer.numAttempts == 1)// SSU
{
peer.numAttempts++;
if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ()))
{
auto address = peer.router->GetSSUAddress (!context.SupportsV6 ());
#if BOOST_VERSION >= 104900
if (!address->host.is_unspecified ()) // we have address now
#else
boost::system::error_code ecode;
address->host.to_string (ecode);
if (!ecode)
#endif
{
m_SSUServer->CreateSession (peer.router, address->host, address->port);
return true;
}
else // we don't have address
{
if (address->addressString.length () > 0) // trying to resolve
{
LogPrint (eLogDebug, "Transports: Resolving SSU ", address->addressString);
SSUResolve (address->addressString, ident);
return true;
}
}
}
}
LogPrint (eLogError, "Transports: No NTCP or SSU addresses available");
peer.Done ();
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
return false;
}
else // otherwise request RI
{
LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested");
i2p::data::netdb.RequestDestination (ident, std::bind (
&Transports::RequestComplete, this, std::placeholders::_1, ident));
}
return true;
}
void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
{
m_Service.post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
}
void Transports::HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident)
{
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
if (r)
{
LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect");
it->second.router = r;
ConnectToPeer (ident, it->second);
}
else
{
LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages");
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
}
}
}
void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident)
{
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(m_Service);
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
std::bind (&Transports::HandleNTCPResolve, this,
std::placeholders::_1, std::placeholders::_2, ident, resolver));
}
void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
{
auto it1 = m_Peers.find (ident);
if (it1 != m_Peers.end ())
{
auto& peer = it1->second;
if (!ecode && peer.router)
{
while (it != boost::asio::ip::tcp::resolver::iterator())
{
auto address = (*it).endpoint ().address ();
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
if (address.is_v4 () || context.SupportsV6 ())
{
auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested
if (addr)
{
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
m_NTCPServer->Connect (address, addr->port, s);
return;
}
break;
}
else
LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported");
it++;
}
}
LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ());
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it1);
}
}
void Transports::SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident)
{
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(m_Service);
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
std::bind (&Transports::HandleSSUResolve, this,
std::placeholders::_1, std::placeholders::_2, ident, resolver));
}
void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
{
auto it1 = m_Peers.find (ident);
if (it1 != m_Peers.end ())
{
auto& peer = it1->second;
if (!ecode && peer.router)
{
while (it != boost::asio::ip::tcp::resolver::iterator())
{
auto address = (*it).endpoint ().address ();
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
if (address.is_v4 () || context.SupportsV6 ())
{
auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested
if (addr)
{
m_SSUServer->CreateSession (peer.router, address, addr->port);
return;
}
break;
}
else
LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported");
it++;
}
}
LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ());
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it1);
}
}
void Transports::CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
{
if (!router) return;
m_Service.post (std::bind (&Transports::PostCloseSession, this, router));
}
void Transports::PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
{
auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr;
if (ssuSession) // try SSU first
{
m_SSUServer->DeleteSession (ssuSession);
LogPrint (eLogDebug, "Transports: SSU session closed");
}
auto ntcpSession = m_NTCPServer ? m_NTCPServer->FindNTCPSession(router->GetIdentHash()) : nullptr;
if (ntcpSession) // try deleting ntcp session too
{
ntcpSession->Terminate ();
LogPrint(eLogDebug, "Transports: NTCP session closed");
}
}
void Transports::DetectExternalIP ()
{
if (m_SSUServer)
{
i2p::context.SetStatus (eRouterStatusTesting);
for (int i = 0; i < 5; i++)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter ();
if (router && router->IsSSU (!context.SupportsV6 ()))
m_SSUServer->CreateSession (router, true); // peer test
else
{
// if not peer test capable routers found pick any
router = i2p::data::netdb.GetRandomRouter ();
if (router && router->IsSSU ())
m_SSUServer->CreateSession (router); // no peer test
}
}
}
else
LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available");
}
void Transports::PeerTest ()
{
if (m_SSUServer)
{
bool statusChanged = false;
for (int i = 0; i < 5; i++)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter ();
if (router && router->IsSSU (!context.SupportsV6 ()))
{
if (!statusChanged)
{
statusChanged = true;
i2p::context.SetStatus (eRouterStatusTesting); // first time only
}
m_SSUServer->CreateSession (router, true); // peer test
}
}
}
}
std::shared_ptr<i2p::crypto::DHKeys> Transports::GetNextDHKeysPair ()
{
return m_DHKeysPairSupplier.Acquire ();
}
void Transports::ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair)
{
m_DHKeysPairSupplier.Return (pair);
}
void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
{
m_Service.post([session, this]()
{
auto remoteIdentity = session->GetRemoteIdentity ();
if (!remoteIdentity) return;
auto ident = remoteIdentity->GetIdentHash ();
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
bool sendDatabaseStore = true;
if (it->second.delayedMessages.size () > 0)
{
// check if first message is our DatabaseStore (publishing)
auto firstMsg = it->second.delayedMessages[0];
if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore &&
i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
sendDatabaseStore = false; // we have it in the list already
}
if (sendDatabaseStore)
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () });
it->second.sessions.push_back (session);
session->SendI2NPMessages (it->second.delayedMessages);
it->second.delayedMessages.clear ();
}
else // incoming connection
{
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} }));
}
});
}
void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
{
m_Service.post([session, this]()
{
auto remoteIdentity = session->GetRemoteIdentity ();
if (!remoteIdentity) return;
auto ident = remoteIdentity->GetIdentHash ();
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
it->second.sessions.remove (session);
if (it->second.sessions.empty ()) // TODO: why?
{
if (it->second.delayedMessages.size () > 0)
ConnectToPeer (ident, it->second);
else
{
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
}
}
}
});
}
bool Transports::IsConnected (const i2p::data::IdentHash& ident) const
{
std::unique_lock<std::mutex> l(m_PeersMutex);
auto it = m_Peers.find (ident);
return it != m_Peers.end ();
}
void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Peers.begin (); it != m_Peers.end (); )
{
if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
{
LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
std::unique_lock<std::mutex> l(m_PeersMutex);
it = m_Peers.erase (it);
}
else
it++;
}
UpdateBandwidth (); // TODO: use separate timer(s) for it
if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test
DetectExternalIP ();
m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
}
}
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const
{
if (m_Peers.empty ()) return nullptr;
std::unique_lock<std::mutex> l(m_PeersMutex);
auto it = m_Peers.begin ();
std::advance (it, rand () % m_Peers.size ());
return it != m_Peers.end () ? it->second.router : nullptr;
}
}
}

View File

@@ -1,155 +0,0 @@
#ifndef TRANSPORTS_H__
#define TRANSPORTS_H__
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <map>
#include <vector>
#include <queue>
#include <string>
#include <memory>
#include <atomic>
#include <boost/asio.hpp>
#include "TransportSession.h"
#include "NTCPSession.h"
#include "SSU.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "Identity.h"
namespace i2p
{
namespace transport
{
class DHKeysPairSupplier
{
public:
DHKeysPairSupplier (int size);
~DHKeysPairSupplier ();
void Start ();
void Stop ();
std::shared_ptr<i2p::crypto::DHKeys> Acquire ();
void Return (std::shared_ptr<i2p::crypto::DHKeys> pair);
private:
void Run ();
void CreateDHKeysPairs (int num);
private:
const int m_QueueSize;
std::queue<std::shared_ptr<i2p::crypto::DHKeys> > m_Queue;
bool m_IsRunning;
std::thread * m_Thread;
std::condition_variable m_Acquired;
std::mutex m_AcquiredMutex;
};
struct Peer
{
int numAttempts;
std::shared_ptr<const i2p::data::RouterInfo> router;
std::list<std::shared_ptr<TransportSession> > sessions;
uint64_t creationTime;
std::vector<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
void Done ()
{
for (auto it: sessions)
it->Done ();
}
};
const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds
class Transports
{
public:
Transports ();
~Transports ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<i2p::crypto::DHKeys> GetNextDHKeysPair ();
void ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair);
void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
void SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs);
void CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
void PeerConnected (std::shared_ptr<TransportSession> session);
void PeerDisconnected (std::shared_ptr<TransportSession> session);
bool IsConnected (const i2p::data::IdentHash& ident) const;
void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; };
void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; };
uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; };
uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; };
uint32_t GetInBandwidth () const { return m_InBandwidth; };
uint32_t GetOutBandwidth () const { return m_OutBandwidth; };
bool IsBandwidthExceeded () const;
size_t GetNumPeers () const { return m_Peers.size (); };
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer () const;
void PeerTest ();
private:
void Run ();
void RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
void PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs);
void PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer);
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident);
void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
void SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident);
void HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
void UpdateBandwidth ();
void DetectExternalIP ();
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::deadline_timer m_PeerCleanupTimer;
NTCPServer * m_NTCPServer;
SSUServer * m_SSUServer;
mutable std::mutex m_PeersMutex;
std::map<i2p::data::IdentHash, Peer> m_Peers;
DHKeysPairSupplier m_DHKeysPairSupplier;
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes;
uint32_t m_InBandwidth, m_OutBandwidth; // bytes per second
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes;
uint64_t m_LastBandwidthUpdateTime;
public:
// for HTTP only
const NTCPServer * GetNTCPServer () const { return m_NTCPServer; };
const SSUServer * GetSSUServer () const { return m_SSUServer; };
const decltype(m_Peers)& GetPeers () const { return m_Peers; };
};
extern Transports transports;
}
}
#endif

View File

@@ -1,872 +0,0 @@
#include <string.h>
#include "I2PEndian.h"
#include <thread>
#include <algorithm>
#include <vector>
#include "Crypto.h"
#include "RouterContext.h"
#include "Log.h"
#include "Timestamp.h"
#include "I2NPProtocol.h"
#include "Transports.h"
#include "NetDb.h"
#include "Tunnel.h"
namespace i2p
{
namespace tunnel
{
Tunnel::Tunnel (std::shared_ptr<const TunnelConfig> config):
TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()),
m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false)
{
}
Tunnel::~Tunnel ()
{
}
void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel)
{
auto numHops = m_Config->GetNumHops ();
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
auto msg = NewI2NPShortMessage ();
*msg->GetPayload () = numRecords;
msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1;
// shuffle records
std::vector<int> recordIndicies;
for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i);
std::random_shuffle (recordIndicies.begin(), recordIndicies.end());
// create real records
uint8_t * records = msg->GetPayload () + 1;
TunnelHopConfig * hop = m_Config->GetFirstHop ();
int i = 0;
while (hop)
{
uint32_t msgID;
if (hop->next) // we set replyMsgID for last hop only
RAND_bytes ((uint8_t *)&msgID, 4);
else
msgID = replyMsgID;
int idx = recordIndicies[i];
hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID);
hop->recordIndex = idx;
i++;
hop = hop->next;
}
// fill up fake records with random data
for (int i = numHops; i < numRecords; i++)
{
int idx = recordIndicies[i];
RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE);
}
// decrypt real records
i2p::crypto::CBCDecryption decryption;
hop = m_Config->GetLastHop ()->prev;
while (hop)
{
decryption.SetKey (hop->replyKey);
// decrypt records after current hop
TunnelHopConfig * hop1 = hop->next;
while (hop1)
{
decryption.SetIV (hop->replyIV);
uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
hop1 = hop1->next;
}
hop = hop->prev;
}
msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild);
// send message
if (outboundTunnel)
outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg);
else
i2p::transport::transports.SendMessage (GetNextIdentHash (), msg);
}
bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
{
LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records.");
i2p::crypto::CBCDecryption decryption;
TunnelHopConfig * hop = m_Config->GetLastHop ();
while (hop)
{
decryption.SetKey (hop->replyKey);
// decrypt records before and including current hop
TunnelHopConfig * hop1 = hop;
while (hop1)
{
auto idx = hop1->recordIndex;
if (idx >= 0 && idx < msg[0])
{
uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE;
decryption.SetIV (hop->replyIV);
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
}
else
LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range");
hop1 = hop1->prev;
}
hop = hop->prev;
}
bool established = true;
hop = m_Config->GetFirstHop ();
while (hop)
{
const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET];
LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
if (profile)
profile->TunnelBuildResponse (ret);
if (ret)
// if any of participants declined the tunnel is not established
established = false;
hop = hop->next;
}
if (established)
{
// create tunnel decryptions from layer and iv keys in reverse order
hop = m_Config->GetLastHop ();
while (hop)
{
auto tunnelHop = new TunnelHop;
tunnelHop->ident = hop->ident;
tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey);
m_Hops.push_back (std::unique_ptr<TunnelHop>(tunnelHop));
hop = hop->prev;
}
m_Config = nullptr;
}
if (established) m_State = eTunnelStateEstablished;
return established;
}
void Tunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
{
const uint8_t * inPayload = in->GetPayload () + 4;
uint8_t * outPayload = out->GetPayload () + 4;
for (auto& it: m_Hops)
{
it->decryption.Decrypt (inPayload, outPayload);
inPayload = outPayload;
}
}
void Tunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions");
}
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > Tunnel::GetPeers () const
{
auto peers = GetInvertedPeers ();
std::reverse (peers.begin (), peers.end ());
return peers;
}
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > Tunnel::GetInvertedPeers () const
{
// hops are in inverted order
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > ret;
for (auto& it: m_Hops)
ret.push_back (it->ident);
return ret;
}
void Tunnel::PrintHops (std::stringstream& s) const
{
for (auto& it: m_Hops)
{
s << "";
s << i2p::data::GetIdentHashAbbreviation (it->ident->GetIdentHash ());
}
}
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg)
{
if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (msg, newMsg);
newMsg->from = shared_from_this ();
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
}
void InboundTunnel::Print (std::stringstream& s) const
{
PrintHops (s);
s << "" << GetTunnelID () << ":me";
}
ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumReceivedBytes (0)
{
}
void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
if (msg)
{
m_NumReceivedBytes += msg->GetLength ();
HandleI2NPMessage (msg);
}
}
void ZeroHopsInboundTunnel::Print (std::stringstream& s) const
{
s << "" << GetTunnelID () << ":me";
}
void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
{
TunnelMessageBlock block;
if (gwHash)
{
block.hash = gwHash;
if (gwTunnel)
{
block.deliveryType = eDeliveryTypeTunnel;
block.tunnelID = gwTunnel;
}
else
block.deliveryType = eDeliveryTypeRouter;
}
else
block.deliveryType = eDeliveryTypeLocal;
block.data = msg;
SendTunnelDataMsg ({block});
}
void OutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
{
std::unique_lock<std::mutex> l(m_SendMutex);
for (auto& it : msgs)
m_Gateway.PutTunnelDataMsg (it);
m_Gateway.SendBuffer ();
}
void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
{
LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ());
}
void OutboundTunnel::Print (std::stringstream& s) const
{
s << GetTunnelID () << ":me";
PrintHops (s);
s << "";
}
ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel ():
OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumSentBytes (0)
{
}
void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
{
for (auto& msg : msgs)
{
switch (msg.deliveryType)
{
case eDeliveryTypeLocal:
i2p::HandleI2NPMessage (msg.data);
break;
case eDeliveryTypeTunnel:
i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
break;
case eDeliveryTypeRouter:
i2p::transport::transports.SendMessage (msg.hash, msg.data);
break;
default:
LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType);
}
}
}
void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const
{
s << GetTunnelID () << ":me ⇒ ";
}
Tunnels tunnels;
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr),
m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0)
{
}
Tunnels::~Tunnels ()
{
}
std::shared_ptr<TunnelBase> Tunnels::GetTunnel (uint32_t tunnelID)
{
auto it = m_Tunnels.find(tunnelID);
if (it != m_Tunnels.end ())
return it->second;
return nullptr;
}
std::shared_ptr<InboundTunnel> Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID)
{
return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels);
}
std::shared_ptr<OutboundTunnel> Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID)
{
return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels);
}
template<class TTunnel>
std::shared_ptr<TTunnel> Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels)
{
auto it = pendingTunnels.find(replyMsgID);
if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending)
{
it->second->SetState (eTunnelStateBuildReplyReceived);
return it->second;
}
return nullptr;
}
std::shared_ptr<InboundTunnel> Tunnels::GetNextInboundTunnel ()
{
std::shared_ptr<InboundTunnel> tunnel;
size_t minReceived = 0;
for (auto it : m_InboundTunnels)
{
if (!it->IsEstablished ()) continue;
if (!tunnel || it->GetNumReceivedBytes () < minReceived)
{
tunnel = it;
minReceived = it->GetNumReceivedBytes ();
}
}
return tunnel;
}
std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
{
if (m_OutboundTunnels.empty ()) return nullptr;
uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0;
std::shared_ptr<OutboundTunnel> tunnel;
for (auto it: m_OutboundTunnels)
{
if (it->IsEstablished ())
{
tunnel = it;
i++;
}
if (i > ind && tunnel) break;
}
return tunnel;
}
std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops,
int numOutboundHops, int numInboundTunnels, int numOutboundTunnels)
{
auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels);
std::unique_lock<std::mutex> l(m_PoolsMutex);
m_Pools.push_back (pool);
return pool;
}
void Tunnels::DeleteTunnelPool (std::shared_ptr<TunnelPool> pool)
{
if (pool)
{
StopTunnelPool (pool);
{
std::unique_lock<std::mutex> l(m_PoolsMutex);
m_Pools.remove (pool);
}
}
}
void Tunnels::StopTunnelPool (std::shared_ptr<TunnelPool> pool)
{
if (pool)
{
pool->SetActive (false);
pool->DetachTunnels ();
}
}
void Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
{
if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second)
m_TransitTunnels.push_back (tunnel);
else
LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists");
}
void Tunnels::Start ()
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Tunnels::Run, this));
}
void Tunnels::Stop ()
{
m_IsRunning = false;
m_Queue.WakeUp ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
}
}
void Tunnels::Run ()
{
std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready
uint64_t lastTs = 0;
while (m_IsRunning)
{
try
{
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
if (msg)
{
uint32_t prevTunnelID = 0, tunnelID = 0;
std::shared_ptr<TunnelBase> prevTunnel;
do
{
std::shared_ptr<TunnelBase> tunnel;
uint8_t typeID = msg->GetTypeID ();
switch (typeID)
{
case eI2NPTunnelData:
case eI2NPTunnelGateway:
{
tunnelID = bufbe32toh (msg->GetPayload ());
if (tunnelID == prevTunnelID)
tunnel = prevTunnel;
else if (prevTunnel)
prevTunnel->FlushTunnelDataMsgs ();
if (!tunnel)
tunnel = GetTunnel (tunnelID);
if (tunnel)
{
if (typeID == eI2NPTunnelData)
tunnel->HandleTunnelDataMsg (msg);
else // tunnel gateway assumed
HandleTunnelGatewayMsg (tunnel, msg);
}
else
LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found");
break;
}
case eI2NPVariableTunnelBuild:
case eI2NPVariableTunnelBuildReply:
case eI2NPTunnelBuild:
case eI2NPTunnelBuildReply:
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
break;
default:
LogPrint (eLogWarning, "Tunnel: unexpected messsage type ", (int) typeID);
}
msg = m_Queue.Get ();
if (msg)
{
prevTunnelID = tunnelID;
prevTunnel = tunnel;
}
else if (tunnel)
tunnel->FlushTunnelDataMsgs ();
}
while (msg);
}
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts - lastTs >= 15) // manage tunnels every 15 seconds
{
ManageTunnels ();
lastTs = ts;
}
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ());
}
}
}
void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg)
{
if (!tunnel)
{
LogPrint (eLogError, "Tunnel: missing tunnel for gateway");
return;
}
const uint8_t * payload = msg->GetPayload ();
uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET);
// we make payload as new I2NP message to send
msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
if (msg->offset + len > msg->len)
{
LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len);
return;
}
msg->len = msg->offset + len;
auto typeID = msg->GetTypeID ();
LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID);
if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply)
// transit DatabaseStore my contain new/updated RI
// or DatabaseSearchReply with new routers
i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg));
tunnel->SendTunnelDataMsg (msg);
}
void Tunnels::ManageTunnels ()
{
ManagePendingTunnels ();
ManageInboundTunnels ();
ManageOutboundTunnels ();
ManageTransitTunnels ();
ManageTunnelPools ();
}
void Tunnels::ManagePendingTunnels ()
{
ManagePendingTunnels (m_PendingInboundTunnels);
ManagePendingTunnels (m_PendingOutboundTunnels);
}
template<class PendingTunnels>
void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels)
{
// check pending tunnel. delete failed or timeout
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();)
{
auto tunnel = it->second;
switch (tunnel->GetState ())
{
case eTunnelStatePending:
if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT)
{
LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted");
// update stats
auto config = tunnel->GetTunnelConfig ();
if (config)
{
auto hop = config->GetFirstHop ();
while (hop)
{
if (hop->ident)
{
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
if (profile)
profile->TunnelNonReplied ();
}
hop = hop->next;
}
}
// delete
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
}
else
it++;
break;
case eTunnelStateBuildFailed:
LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted");
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
break;
case eTunnelStateBuildReplyReceived:
// intermediate state, will be either established of build failed
it++;
break;
default:
// success
it = pendingTunnels.erase (it);
m_NumSuccesiveTunnelCreations++;
}
}
}
void Tunnels::ManageOutboundTunnels ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
{
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired");
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
// we don't have outbound tunnels in m_Tunnels
it = m_OutboundTunnels.erase (it);
}
else
{
if (tunnel->IsEstablished ())
{
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
tunnel->SetIsRecreated ();
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->RecreateOutboundTunnel (tunnel);
}
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
tunnel->SetState (eTunnelStateExpiring);
}
it++;
}
}
}
if (m_OutboundTunnels.size () < 5)
{
// trying to create one more oubound tunnel
auto inboundTunnel = GetNextInboundTunnel ();
auto router = i2p::data::netdb.GetRandomRouter ();
if (!inboundTunnel || !router) return;
LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel");
CreateTunnel<OutboundTunnel> (
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () },
inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())
);
}
}
void Tunnels::ManageInboundTunnels ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
{
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired");
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
m_Tunnels.erase (tunnel->GetTunnelID ());
it = m_InboundTunnels.erase (it);
}
else
{
if (tunnel->IsEstablished ())
{
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
tunnel->SetIsRecreated ();
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->RecreateInboundTunnel (tunnel);
}
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
tunnel->SetState (eTunnelStateExpiring);
}
it++;
}
}
}
if (m_InboundTunnels.empty ())
{
LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel");
CreateZeroHopsInboundTunnel ();
CreateZeroHopsOutboundTunnel ();
if (!m_ExploratoryPool)
{
m_ExploratoryPool = CreateTunnelPool (2, 2, 5, 5); // 2-hop exploratory, 5 tunnels
m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ());
}
return;
}
if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5)
{
// trying to create one more inbound tunnel
auto router = i2p::data::netdb.GetRandomRouter ();
if (!router) {
LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel");
return;
}
LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel");
CreateTunnel<InboundTunnel> (
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () })
);
}
}
void Tunnels::ManageTransitTunnels ()
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
{
LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
m_Tunnels.erase (tunnel->GetTunnelID ());
it = m_TransitTunnels.erase (it);
}
else
it++;
}
}
void Tunnels::ManageTunnelPools ()
{
std::unique_lock<std::mutex> l(m_PoolsMutex);
for (auto it: m_Pools)
{
auto pool = it;
if (pool && pool->IsActive ())
{
pool->CreateTunnels ();
pool->TestTunnels ();
}
}
}
void Tunnels::PostTunnelData (std::shared_ptr<I2NPMessage> msg)
{
if (msg) m_Queue.Put (msg);
}
void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
{
m_Queue.Put (msgs);
}
template<class TTunnel>
std::shared_ptr<TTunnel> Tunnels::CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel)
{
auto newTunnel = std::make_shared<TTunnel> (config);
uint32_t replyMsgID;
RAND_bytes ((uint8_t *)&replyMsgID, 4);
AddPendingTunnel (replyMsgID, newTunnel);
newTunnel->Build (replyMsgID, outboundTunnel);
return newTunnel;
}
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel)
{
m_PendingInboundTunnels[replyMsgID] = tunnel;
}
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel)
{
m_PendingOutboundTunnels[replyMsgID] = tunnel;
}
void Tunnels::AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel)
{
// we don't need to insert it to m_Tunnels
m_OutboundTunnels.push_back (newTunnel);
auto pool = newTunnel->GetTunnelPool ();
if (pool && pool->IsActive ())
pool->TunnelCreated (newTunnel);
else
newTunnel->SetTunnelPool (nullptr);
}
void Tunnels::AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel)
{
if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second)
{
m_InboundTunnels.push_back (newTunnel);
auto pool = newTunnel->GetTunnelPool ();
if (!pool)
{
// build symmetric outbound tunnel
CreateTunnel<OutboundTunnel> (std::make_shared<TunnelConfig>(newTunnel->GetInvertedPeers (),
newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()),
GetNextOutboundTunnel ());
}
else
{
if (pool->IsActive ())
pool->TunnelCreated (newTunnel);
else
newTunnel->SetTunnelPool (nullptr);
}
}
else
LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists");
}
void Tunnels::CreateZeroHopsInboundTunnel ()
{
auto inboundTunnel = std::make_shared<ZeroHopsInboundTunnel> ();
inboundTunnel->SetState (eTunnelStateEstablished);
m_InboundTunnels.push_back (inboundTunnel);
m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel;
}
void Tunnels::CreateZeroHopsOutboundTunnel ()
{
auto outboundTunnel = std::make_shared<ZeroHopsOutboundTunnel> ();
outboundTunnel->SetState (eTunnelStateEstablished);
m_OutboundTunnels.push_back (outboundTunnel);
// we don't insert into m_Tunnels
}
int Tunnels::GetTransitTunnelsExpirationTimeout ()
{
int timeout = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
// TODO: possible race condition with I2PControl
for (auto it: m_TransitTunnels)
{
int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
if (t > timeout) timeout = t;
}
return timeout;
}
size_t Tunnels::CountTransitTunnels() const
{
// TODO: locking
return m_TransitTunnels.size();
}
size_t Tunnels::CountInboundTunnels() const
{
// TODO: locking
return m_InboundTunnels.size();
}
size_t Tunnels::CountOutboundTunnels() const
{
// TODO: locking
return m_OutboundTunnels.size();
}
#ifdef ANDROID_ARM7A
template std::shared_ptr<InboundTunnel> Tunnels::CreateTunnel<InboundTunnel>(std::shared_ptr<TunnelConfig>, std::shared_ptr<OutboundTunnel>);
template std::shared_ptr<OutboundTunnel> Tunnels::CreateTunnel<OutboundTunnel>(std::shared_ptr<TunnelConfig>, std::shared_ptr<OutboundTunnel>);
#endif
}
}

251
Tunnel.h
View File

@@ -1,251 +0,0 @@
#ifndef TUNNEL_H__
#define TUNNEL_H__
#include <inttypes.h>
#include <map>
#include <unordered_map>
#include <list>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include "Queue.h"
#include "Crypto.h"
#include "TunnelConfig.h"
#include "TunnelPool.h"
#include "TransitTunnel.h"
#include "TunnelEndpoint.h"
#include "TunnelGateway.h"
#include "TunnelBase.h"
#include "I2NPProtocol.h"
namespace i2p
{
namespace tunnel
{
const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes
const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute
const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes
const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds
const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message
enum TunnelState
{
eTunnelStatePending,
eTunnelStateBuildReplyReceived,
eTunnelStateBuildFailed,
eTunnelStateEstablished,
eTunnelStateTestFailed,
eTunnelStateFailed,
eTunnelStateExpiring
};
class OutboundTunnel;
class InboundTunnel;
class Tunnel: public TunnelBase
{
struct TunnelHop
{
std::shared_ptr<const i2p::data::IdentityEx> ident;
i2p::crypto::TunnelDecryption decryption;
};
public:
Tunnel (std::shared_ptr<const TunnelConfig> config);
~Tunnel ();
void Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
std::shared_ptr<const TunnelConfig> GetTunnelConfig () const { return m_Config; }
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetPeers () const;
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetInvertedPeers () const;
TunnelState GetState () const { return m_State; };
void SetState (TunnelState state) { m_State = state; };
bool IsEstablished () const { return m_State == eTunnelStateEstablished; };
bool IsFailed () const { return m_State == eTunnelStateFailed; };
bool IsRecreated () const { return m_IsRecreated; };
void SetIsRecreated () { m_IsRecreated = true; };
std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
bool HandleTunnelBuildResponse (uint8_t * msg, size_t len);
virtual void Print (std::stringstream&) const {};
// implements TunnelBase
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
protected:
void PrintHops (std::stringstream& s) const;
private:
std::shared_ptr<const TunnelConfig> m_Config;
std::vector<std::unique_ptr<TunnelHop> > m_Hops;
std::shared_ptr<TunnelPool> m_Pool; // pool, tunnel belongs to, or null
TunnelState m_State;
bool m_IsRecreated;
};
class OutboundTunnel: public Tunnel
{
public:
OutboundTunnel (std::shared_ptr<const TunnelConfig> config):
Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg);
virtual void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; };
virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); };
void Print (std::stringstream& s) const;
// implements TunnelBase
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
private:
std::mutex m_SendMutex;
TunnelGateway m_Gateway;
i2p::data::IdentHash m_EndpointIdentHash;
};
class InboundTunnel: public Tunnel, public std::enable_shared_from_this<InboundTunnel>
{
public:
InboundTunnel (std::shared_ptr<const TunnelConfig> config): Tunnel (config), m_Endpoint (true) {};
void HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg);
virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
void Print (std::stringstream& s) const;
private:
TunnelEndpoint m_Endpoint;
};
class ZeroHopsInboundTunnel: public InboundTunnel
{
public:
ZeroHopsInboundTunnel ();
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
void Print (std::stringstream& s) const;
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
private:
size_t m_NumReceivedBytes;
};
class ZeroHopsOutboundTunnel: public OutboundTunnel
{
public:
ZeroHopsOutboundTunnel ();
void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs);
void Print (std::stringstream& s) const;
size_t GetNumSentBytes () const { return m_NumSentBytes; };
private:
size_t m_NumSentBytes;
};
class Tunnels
{
public:
Tunnels ();
~Tunnels ();
void Start ();
void Stop ();
std::shared_ptr<InboundTunnel> GetPendingInboundTunnel (uint32_t replyMsgID);
std::shared_ptr<OutboundTunnel> GetPendingOutboundTunnel (uint32_t replyMsgID);
std::shared_ptr<InboundTunnel> GetNextInboundTunnel ();
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel ();
std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
std::shared_ptr<TunnelBase> GetTunnel (uint32_t tunnelID);
int GetTransitTunnelsExpirationTimeout ();
void AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
void PostTunnelData (std::shared_ptr<I2NPMessage> msg);
void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
template<class TTunnel>
std::shared_ptr<TTunnel> CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel);
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel);
std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops,
int numOuboundHops, int numInboundTunnels, int numOutboundTunnels);
void DeleteTunnelPool (std::shared_ptr<TunnelPool> pool);
void StopTunnelPool (std::shared_ptr<TunnelPool> pool);
private:
template<class TTunnel>
std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels);
void HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg);
void Run ();
void ManageTunnels ();
void ManageOutboundTunnels ();
void ManageInboundTunnels ();
void ManageTransitTunnels ();
void ManagePendingTunnels ();
template<class PendingTunnels>
void ManagePendingTunnels (PendingTunnels& pendingTunnels);
void ManageTunnelPools ();
void CreateZeroHopsInboundTunnel ();
void CreateZeroHopsOutboundTunnel ();
private:
bool m_IsRunning;
std::thread * m_Thread;
std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_PendingInboundTunnels; // by replyMsgID
std::map<uint32_t, std::shared_ptr<OutboundTunnel> > m_PendingOutboundTunnels; // by replyMsgID
std::list<std::shared_ptr<InboundTunnel> > m_InboundTunnels;
std::list<std::shared_ptr<OutboundTunnel> > m_OutboundTunnels;
std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
std::unordered_map<uint32_t, std::shared_ptr<TunnelBase> > m_Tunnels; // tunnelID->tunnel known by this id
std::mutex m_PoolsMutex;
std::list<std::shared_ptr<TunnelPool>> m_Pools;
std::shared_ptr<TunnelPool> m_ExploratoryPool;
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
// some stats
int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
public:
// for HTTP only
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; };
size_t CountTransitTunnels() const;
size_t CountInboundTunnels() const;
size_t CountOutboundTunnels() const;
int GetQueueSize () { return m_Queue.GetSize (); };
int GetTunnelCreationSuccessRate () const // in percents
{
int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations;
return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0;
}
};
extern Tunnels tunnels;
}
}
#endif

View File

@@ -1,72 +0,0 @@
#ifndef TUNNEL_BASE_H__
#define TUNNEL_BASE_H__
#include <inttypes.h>
#include <memory>
#include "Timestamp.h"
#include "I2NPProtocol.h"
#include "Identity.h"
namespace i2p
{
namespace tunnel
{
const size_t TUNNEL_DATA_MSG_SIZE = 1028;
const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008;
const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003;
enum TunnelDeliveryType
{
eDeliveryTypeLocal = 0,
eDeliveryTypeTunnel = 1,
eDeliveryTypeRouter = 2
};
struct TunnelMessageBlock
{
TunnelDeliveryType deliveryType;
i2p::data::IdentHash hash;
uint32_t tunnelID;
std::shared_ptr<I2NPMessage> data;
};
class TunnelBase
{
public:
TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent):
m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent),
m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {};
virtual ~TunnelBase () {};
virtual void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg) = 0;
virtual void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) = 0;
virtual void FlushTunnelDataMsgs () {};
virtual void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) = 0;
uint32_t GetNextTunnelID () const { return m_NextTunnelID; };
const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; };
virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side
uint32_t GetCreationTime () const { return m_CreationTime; };
void SetCreationTime (uint32_t t) { m_CreationTime = t; };
private:
uint32_t m_TunnelID, m_NextTunnelID;
i2p::data::IdentHash m_NextIdent;
uint32_t m_CreationTime; // seconds since epoch
};
struct TunnelCreationTimeCmp
{
bool operator() (std::shared_ptr<const TunnelBase> t1, std::shared_ptr<const TunnelBase> t2) const
{
if (t1->GetCreationTime () != t2->GetCreationTime ())
return t1->GetCreationTime () > t2->GetCreationTime ();
else
return t1 < t2;
};
};
}
}
#endif

View File

@@ -1,248 +0,0 @@
#ifndef TUNNEL_CONFIG_H__
#define TUNNEL_CONFIG_H__
#include <inttypes.h>
#include <sstream>
#include <vector>
#include <memory>
#include "Crypto.h"
#include "Identity.h"
#include "RouterContext.h"
#include "Timestamp.h"
namespace i2p
{
namespace tunnel
{
struct TunnelHopConfig
{
std::shared_ptr<const i2p::data::IdentityEx> ident;
i2p::data::IdentHash nextIdent;
uint32_t tunnelID, nextTunnelID;
uint8_t layerKey[32];
uint8_t ivKey[32];
uint8_t replyKey[32];
uint8_t replyIV[16];
bool isGateway, isEndpoint;
TunnelHopConfig * next, * prev;
int recordIndex; // record # in tunnel build message
TunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r)
{
RAND_bytes (layerKey, 32);
RAND_bytes (ivKey, 32);
RAND_bytes (replyKey, 32);
RAND_bytes (replyIV, 16);
RAND_bytes ((uint8_t *)&tunnelID, 4);
isGateway = true;
isEndpoint = true;
ident = r;
//nextRouter = nullptr;
nextTunnelID = 0;
next = nullptr;
prev = nullptr;
}
void SetNextIdent (const i2p::data::IdentHash& ident)
{
nextIdent = ident;
isEndpoint = false;
RAND_bytes ((uint8_t *)&nextTunnelID, 4);
}
void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent)
{
nextIdent = replyIdent;
nextTunnelID = replyTunnelID;
isEndpoint = true;
}
void SetNext (TunnelHopConfig * n)
{
next = n;
if (next)
{
next->prev = this;
next->isGateway = false;
isEndpoint = false;
nextIdent = next->ident->GetIdentHash ();
nextTunnelID = next->tunnelID;
}
}
void SetPrev (TunnelHopConfig * p)
{
prev = p;
if (prev)
{
prev->next = this;
prev->isEndpoint = false;
isGateway = false;
}
}
void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID) const
{
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID);
memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32);
htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID);
memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16);
uint8_t flag = 0;
if (isGateway) flag |= 0x80;
if (isEndpoint) flag |= 0x40;
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag;
htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ());
htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET);
i2p::crypto::ElGamalEncryption elGamalEncryption (ident->GetEncryptionPublicKey ());
elGamalEncryption.Encrypt (clearText, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET);
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
}
};
class TunnelConfig
{
public:
TunnelConfig (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers) // inbound
{
CreatePeers (peers);
m_LastHop->SetNextIdent (i2p::context.GetIdentHash ());
}
TunnelConfig (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers,
uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound
{
CreatePeers (peers);
m_FirstHop->isGateway = false;
m_LastHop->SetReplyHop (replyTunnelID, replyIdent);
}
~TunnelConfig ()
{
TunnelHopConfig * hop = m_FirstHop;
while (hop)
{
auto tmp = hop;
hop = hop->next;
delete tmp;
}
}
TunnelHopConfig * GetFirstHop () const
{
return m_FirstHop;
}
TunnelHopConfig * GetLastHop () const
{
return m_LastHop;
}
int GetNumHops () const
{
int num = 0;
TunnelHopConfig * hop = m_FirstHop;
while (hop)
{
num++;
hop = hop->next;
}
return num;
}
virtual bool IsInbound () const { return m_FirstHop->isGateway; }
virtual uint32_t GetTunnelID () const
{
if (!m_FirstHop) return 0;
return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID;
}
virtual uint32_t GetNextTunnelID () const
{
if (!m_FirstHop) return 0;
return m_FirstHop->tunnelID;
}
virtual const i2p::data::IdentHash& GetNextIdentHash () const
{
return m_FirstHop->ident->GetIdentHash ();
}
virtual const i2p::data::IdentHash& GetLastIdentHash () const
{
return m_LastHop->ident->GetIdentHash ();
}
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetPeers () const
{
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers;
TunnelHopConfig * hop = m_FirstHop;
while (hop)
{
peers.push_back (hop->ident);
hop = hop->next;
}
return peers;
}
protected:
// this constructor can't be called from outside
TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr)
{
}
private:
template<class Peers>
void CreatePeers (const Peers& peers)
{
TunnelHopConfig * prev = nullptr;
for (auto it: peers)
{
auto hop = new TunnelHopConfig (it);
if (prev)
prev->SetNext (hop);
else
m_FirstHop = hop;
prev = hop;
}
m_LastHop = prev;
}
private:
TunnelHopConfig * m_FirstHop, * m_LastHop;
};
class ZeroHopsTunnelConfig: public TunnelConfig
{
public:
ZeroHopsTunnelConfig () { RAND_bytes ((uint8_t *)&m_TunnelID, 4);};
bool IsInbound () const { return true; }; // TODO:
uint32_t GetTunnelID () const { return m_TunnelID; };
uint32_t GetNextTunnelID () const { return m_TunnelID; };
const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); };
const i2p::data::IdentHash& GetLastIdentHash () const { return i2p::context.GetIdentHash (); };
private:
uint32_t m_TunnelID;
};
}
}
#endif

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